如何在 iOS 14+ 及更低版本上将文件选择器添加到应用程序
Posted
技术标签:
【中文标题】如何在 iOS 14+ 及更低版本上将文件选择器添加到应用程序【英文标题】:How to add file picker to the app on iOS 14+ and lower 【发布时间】:2020-10-01 05:02:01 【问题描述】:我是 ios 开发的新手,所以我将在这里展示和询问的一些事情可能很愚蠢,请不要生气 :) 所以,我需要在我的应用程序中添加从本地存储中选择文件的支持。此功能将用于选择文件 -> 编码为 Base64,然后发送到远程服务器。现在我在将这个功能添加到我的应用程序时遇到了一些问题。我找到了this tutorial 并做了这里提到的一切:
添加导入 - import MobileCoreServices
添加实现 - UIDocumentPickerDelegate
添加此代码范围以显示选择器:
let documentPicker = UIDocumentPickerViewController(documentTypes: [String(kUTTypeText),String(kUTTypeContent),String(kUTTypeItem),String(kUTTypeData)], in: .import)
documentPicker.delegate = self
self.present(documentPicker, animated: true)
还添加了所选文件的处理程序:
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL])
print(urls)
一般文件选择器出现在模拟器屏幕上,但我在 XCode 中看到警告:
'init(documentTypes:in:)' was deprecated in iOS 14.0
我访问了official guideline,在这里还找到了有关弃用某些方法的类似信息。那么,如何通过与最新iOS版本完全兼容的方式来解决我的文件选择问题。还有另一个问题 - 我如何编码选定的文件?现在我有能力选择文件并打印它的位置,但我需要获取它的数据,如名称、编码内容和其他一些数据。也许有人面临类似的问题并知道解决方案?我需要在普通的viewcontroller中添加它,所以当我尝试添加这个实现时:
UIDocumentPickerViewController
我看到了这样的错误信息:
Multiple inheritance from classes 'UIViewController' and 'UIDocumentPickerViewController'
我会很高兴任何信息:教程或建议 :)
【问题讨论】:
在那里找到了类似的线程 -> ***.com/questions/62653008/… 。希望对你有帮助 这能回答你的问题吗? Initialization of UIDocumentPickerViewController in iOS 14 @AppDev.,等一下,我正在检查 :) @AppDev.,一般来说效果很好,但我还是不明白如何从挑选的文件中获取文件数据?你知道吗? 【参考方案1】:一旦您有了文件 URL,您就可以使用该 URL 来检索它包含的数据。获得数据后,您可以将其转换为 Base64 并将其发送到服务器。您没有提供有关如何将其发送到服务器的信息,但其余部分可能如下所示:
func sendFileWithURL(_ url: URL, completion: @escaping ((_ error: Error?) -> Void))
func finish(_ error: Error?)
DispatchQueue.main.async
completion(error)
DispatchQueue(label: "DownloadingFileData." + UUID().uuidString).async
do
let data: Data = try Data(contentsOf: url)
let base64String = data.base64EncodedString()
// TODO: send string to server and call the completion
finish(nil)
catch
finish(error)
你会使用它
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL])
urls.forEach sendFileWithURL($0) <#Your code here#>
分解:
要获取文件数据,您可以使用Data(contentsOf: url)
。此方法甚至适用于远程文件,因此您可以例如在您可以访问的 Internet 上的任何地方使用图像链接的 URL。重要的是要知道此方法会暂停您的线程,这通常不是您想要的。
为避免中断当前线程,我们使用DispatchQueue(label: "DownloadingFileData." + UUID().uuidString)
创建一个新队列。队列的名称不是很重要,但在调试时很有用。
收到数据后,我们使用data.base64EncodedString()
将其转换为Base64 字符串,然后可以将这些数据发送到服务器。您只需要填写TODO:
部分即可。
检索您的文件数据可能会出现一些错误。也许访问限制或文件不再存在或没有互联网连接......这是通过抛出来处理的。如果带有try
的语句由于任何原因失败,则catch
部分将执行并且您会收到错误消息。
由于所有这些都是在后台线程上完成的,因此返回主线程通常是有意义的。这就是finish
函数的作用。如果您不需要,您可以简单地删除它并拥有:
func sendFileWithURL(_ url: URL, completion: @escaping ((_ error: Error?) -> Void))
DispatchQueue(label: "DownloadingFileData." + UUID().uuidString).async
do
let data: Data = try Data(contentsOf: url)
let base64String = data.base64EncodedString()
// TODO: send string to server and call the completion
completion(nil)
catch
completion(error)
在这种方法中还有其他需要考虑的事情。例如,您可以查看用户是否选择了多个文件,然后每个文件都将打开自己的队列并启动该过程。这意味着,如果用户选择多个文件,则可能在某些时候它们中的许多或全部将被加载到内存中。这可能会占用太多内存并使您的应用程序崩溃。由您决定此方法是否适合您或您希望序列化该过程。使用队列进行序列化应该非常简单。您只需要一个:
private lazy var fileProcessingQueue: DispatchQueue = DispatchQueue(label: "DownloadingFileData.main")
func sendFileWithURL(_ url: URL, completion: @escaping ((_ error: Error?) -> Void))
func finish(_ error: Error?)
DispatchQueue.main.async
completion(error)
fileProcessingQueue.async
do
let data: Data = try Data(contentsOf: url)
let base64String = data.base64EncodedString()
// TODO: send string to server and call the completion
finish(nil)
catch
finish(error)
现在一项操作将在另一项操作开始之前完成。但这可能仅适用于获取文件数据和转换为 base64 字符串。如果上传是在另一个线程上完成的(通常是这样),那么您可能仍然有多个正在进行的请求,其中可能包含上传所需的所有数据。
【讨论】:
感谢您的回复,我正在通过 Alamofire 向服务器发送数据,但我认为它在从文件中检索数据的一般机制中并不那么重要,但您能否添加一些关于获取文件名和大小,因为我在您的答案中没有看到它?谢谢你:) @Andrew 不确定您是否要求这样做。但是您可以通过FileManager.default.attributesOfItem(atPath: url.path)
获得大部分内容。至于文件名和扩展名,即使URL
也有相应的方法。
一般我已经添加了您的代码,但我有两个问题: 1. 我必须单独添加所有可能的文件类型,或者可以添加一种通用文件类型? 2. 选择图片后出现这样的错误-[DocumentManager] Failed to associate thumbnails for picked URL
,请问是什么原因导致的?
@Andrew 我会为这两个问题分别提出问题。因为其他人可能比我更好地为你解决这个问题。过去我没有过多处理文档 API。【参考方案2】:
我决定发布我自己的问题解决方案。由于我是 ios 开发新手,我的答案可能包含一些逻辑问题:) 首先,我在按下 Attach
按钮后添加了一些选择文件类型的对话框:
@IBAction func attachFile(_ sender: UIBarButtonItem)
let attachSheet = UIAlertController(title: nil, message: "File attaching", preferredStyle: .actionSheet)
attachSheet.addAction(UIAlertAction(title: "File", style: .default,handler: (action) in
let supportedTypes: [UTType] = [UTType.png,UTType.jpeg]
let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: supportedTypes)
documentPicker.delegate = self
documentPicker.allowsMultipleSelection = false
documentPicker.shouldShowFileExtensions = true
self.present(documentPicker, animated: true, completion: nil)
))
attachSheet.addAction(UIAlertAction(title: "Photo/Video", style: .default,handler: (action) in
self.chooseImage()
))
attachSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel))
self.present(attachSheet, animated: true, completion: nil)
然后当用户选择File
时,他将被移动到我处理他的选择的普通目录:
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL])
var selectedFileData = [String:String]()
let file = urls[0]
do
let fileData = try Data.init(contentsOf: file.absoluteURL)
selectedFileData["filename"] = file.lastPathComponent
selectedFileData["data"] = fileData.base64EncodedString(options: .lineLength64Characters)
catch
print("contents could not be loaded")
正如您在上面的范围中看到的那样,我在将数据发送到服务器之前形成了用于存储数据的特殊字典。在这里您还可以看到 Base64 的编码。
当用户在警报对话框中按下Photo/Video
项目时,他将被移动到图库以进行图片选择:
func chooseImage()
imagePicker.allowsEditing = false
imagePicker.sourceType = .photoLibrary
present(imagePicker, animated: true, completion: nil)
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any])
var selectedImageData = [String:String]()
guard let fileUrl = info[UIImagePickerController.InfoKey.imageURL] as? URL else return
print(fileUrl.lastPathComponent)
if let pickedImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage
selectedImageData["filename"] = fileUrl.lastPathComponent
selectedImageData["data"] = pickedImage.pngData()?.base64EncodedString(options: .lineLength64Characters)
dismiss(animated: true, completion: nil)
func imagePickerControllerDidCancel(_ picker: UIImagePickerController)
dismiss(animated: true, completion: nil)
通过我的方法,所有文件内容都将被编码为base64字符串。
附:我也很高兴@MaticOblak,因为他向我展示了我的研究和最终解决方案的起点。他的解决方案也不错,但我设法以对我的项目更方便的方式解决了我的问题:)
【讨论】:
以上是关于如何在 iOS 14+ 及更低版本上将文件选择器添加到应用程序的主要内容,如果未能解决你的问题,请参考以下文章
iOS5 情节提要错误:情节提要在 iOS 4.3 及更低版本上不可用
iOS BLE 蓝牙 8.1 及更低版本 BLE 订阅特征通知无响应
AWS Device Farm目前支持iOS 10.3.3及更低版本的XCTest
从 iOS 11 上的左侧菜单导航后 Xcode 9 导航栏问题不在 iOS 10.3 及更低版本上