I'm using UIDocumentPickerViewController
to let the user select a file from iCloud Drive for uploading to the backend.
Most of the time, it works correctly. However, sometimes (especially when the internet connection is spotty)documentPicker:didPickDocumentAtURL:
gives a url that does not actually exist on the filesystem, and any attempt to use it returns a NSError "No such file or directory".
What is the correct way to handle this? I'm thinking about using NSFileManager fileExistsAtPath:
and tell the user to try again if it doesn't exist. But that doesn't sound very user friendly. Is there a way to get the real error reason from iCloud Drive and perhaps tell iCloud Drive to try again?
The relevant parts of the code:
@IBAction func add(sender: UIBarButtonItem) {
let documentMenu = UIDocumentMenuViewController(
documentTypes: [kUTTypeImage as String],
inMode: .Import)
documentMenu.delegate = self
documentMenu.popoverPresentationController?.barButtonItem = sender
presentViewController(documentMenu, animated: true, completion: nil)
}
func documentMenu(documentMenu: UIDocumentMenuViewController, didPickDocumentPicker documentPicker: UIDocumentPickerViewController) {
documentPicker.delegate = self
documentPicker.popoverPresentationController?.sourceView = self.view
presentViewController(documentPicker, animated: true, completion: nil)
}
func documentPicker(controller: UIDocumentPickerViewController, didPickDocumentAtURL url: NSURL) {
print("original URL", url)
url.startAccessingSecurityScopedResource()
var error: NSError?
NSFileCoordinator().coordinateReadingItemAtURL(
url, options: .ForUploading, error: &error) { url in
print("coordinated URL", url)
}
if let error = error {
print(error)
}
url.stopAccessingSecurityScopedResource()
}
I reproduced this by adding two large images (~5MiB each) to iCloud Drive on OS X and opening only one of them (a synced file.bmp
) on an iPhone and not opening the other (an unsynced file.bmp
). And then turned off WiFi. Then I tried to select them in my application:
The synced file:
original URL file:///private/var/mobile/Containers/Data/Application/CE70EE57-B906-4BF8-B351-A57110BE2B01/tmp/example.com.demo-Inbox/a%20synced%20file.bmp
coordinated URL file:///private/var/mobile/Containers/Data/Application/CE70EE57-B906-4BF8-B351-A57110BE2B01/tmp/CoordinatedZipFileDR7e5I/a%20synced%20file.bmp
The unsynced file:
original URL file:///private/var/mobile/Containers/Data/Application/CE70EE57-B906-4BF8-B351-A57110BE2B01/tmp/example.com.demo-Inbox/an%20unsynced%20file.bmp
Error Domain=NSCocoaErrorDomain Code=260 "The file “an unsynced file.bmp” couldn’t be opened because there is no such file." UserInfo={NSURL=file:///private/var/mobile/Containers/Data/Application/CE70EE57-B906-4BF8-B351-A57110BE2B01/tmp/example.com.demo-Inbox/an%20unsynced%20file.bmp, NSFilePath=/private/var/mobile/Containers/Data/Application/CE70EE57-B906-4BF8-B351-A57110BE2B01/tmp/example.com.demo-Inbox/an unsynced file.bmp, NSUnderlyingError=0x15fee1210 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}
Similar problem occurred to me. I have document picker initialized like this:
var documentPicker: UIDocumentPickerViewController = UIDocumentPickerViewController(documentTypes: ["public.data"], in: .import)
Which means that files are copied to app_id-Inbox
directory after they are selected in documentPicker
. When delegate method documentPicker(_:didPickDocumentsAt:)
gets called it gives URLs which are pointing to files that are located in app_id-Inbox
directory.
After some time (without closing app) those URLs were pointing to files that are not existing. That happened because app_id-Inbox
in tmp/
folder was cleared meanwhile. For example I pick documents, show them in table view and leave iPhone on that screen for like a minute, then when I try to click on specific documents which opens file in QLPreviewController
using URL provided from documentPicker
it returns file not existing.
This seems like a bug because Apple's documentation states following here
UIDocumentPickerModeImport
The URLs refer to a copy of the selected documents. These documents are temporary files. They remain available only until your application terminates. To keep a permanent copy, move these files to a permanent location inside your sandbox.
It clearly says until application terminates, but in my case that was around minute of not opening that URL.
Move files from app_id-Inbox
folder to tmp/
or any other directory then use URLs that are pointing to new location.
Swift 4
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
let newUrls = urls.compactMap { (url: URL) -> URL? in
// Create file URL to temporary folder
var tempURL = URL(fileURLWithPath: NSTemporaryDirectory())
// Apend filename (name+extension) to URL
tempURL.appendPathComponent(url.lastPathComponent)
do {
// If file with same name exists remove it (replace file with new one)
if FileManager.default.fileExists(atPath: tempURL.path) {
try FileManager.default.removeItem(atPath: tempURL.path)
}
// Move file from app_id-Inbox to tmp/filename
try FileManager.default.moveItem(atPath: url.path, toPath: tempURL.path)
return tempURL
} catch {
print(error.localizedDescription)
return nil
}
}
// ... do something with URLs
}
Although system will take care of /tmp
directory it's recommended to clear its content when it's not needed anymore.