私は UIDocumentPickerViewController
を使用して、ユーザーがバックエンドにアップロードするためにiCloudドライブからファイルを選択できるようにしています。
ほとんどの場合、正しく機能します。ただし、場合によっては(特にインターネット接続が不安定な場合) documentPicker:didPickDocumentAtURL:
は、ファイルシステムに実際には存在しないURLを提供し、それを使用しようとすると、NSError「そのようなファイルまたはディレクトリはありません」が返されます。
これを処理する正しい方法は何ですか? NSFileManager fileExistsAtPath:
存在しない場合は、再試行するようにユーザーに指示します。しかし、それはあまりユーザーフレンドリーに聞こえません。 iCloudドライブから本当のエラー理由を取得し、おそらくiCloudドライブに再試行するように指示する方法はありますか?
コードの関連部分:
@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()
}
OS XのiCloudドライブに2つの大きな画像(それぞれ約5MiB)を追加し、そのうちの1つだけを開くことでこれを再現しました(a synced file.bmp
)iPhoneで、もう一方を開かない(an unsynced file.bmp
)。そして、WiFiをオフにしました。次に、アプリケーションでそれらを選択しようとしました。
同期されたファイル:
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
同期されていないファイル:
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"}}
同様の問題が私にも起こりました。ドキュメントピッカーを次のように初期化しました。
_var documentPicker: UIDocumentPickerViewController = UIDocumentPickerViewController(documentTypes: ["public.data"], in: .import)
_
つまり、ファイルはdocumentPicker
で選択された後、_app_id-Inbox
_ディレクトリにコピーされます。デリゲートメソッドdocumentPicker(_:didPickDocumentsAt:)
が呼び出されると、_app_id-Inbox
_ディレクトリにあるファイルを指すURLが提供されます。
しばらくすると(アプリを閉じずに)、これらのURLは存在しないファイルを指していました。これは、_app_id-Inbox
_フォルダー内の_tmp/
_がその間にクリアされたために発生しました。たとえば、ドキュメントを選択してテーブルビューに表示し、iPhoneをその画面に1分ほど置いたままにします。次に、QLPreviewController
から提供されたURLを使用してdocumentPicker
でファイルを開く特定のドキュメントをクリックしようとすると、存在しないファイルが返されます。
Appleのドキュメントには次のように記載されているため、これはバグのようです ここ
UIDocumentPickerModeImport
URLは、選択したドキュメントのコピーを参照します。これらのドキュメントは一時ファイルです。これらは、アプリケーションが終了するまでのみ使用できます。永続的なコピーを保持するには、これらのファイルをサンドボックス内の永続的な場所に移動します。
アプリケーションが終了するまでとはっきりと書かれていますが、私の場合は、そのURLを開かなかったのは約1分でした。
ファイルを_app_id-Inbox
_フォルダーから_tmp/
_またはその他のディレクトリに移動し、新しい場所を指すURLを使用します。
Swift 4
_func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
let newUrls = urls.flatMap { (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
}
_
システムは_/tmp
_ディレクトリを処理しますが、不要になったときにコンテンツをクリアすることをお勧めします。
ドキュメントピッカーを使用している間、ファイルシステムを使用するには、以下のようにURLをファイルパスに変換します。
var filePath = urls[0].absoluteString
filePath = filePath.replacingOccurrences(of: "file:/", with: "")//making url to file path
問題は、tmpディレクトリが何らかの方法でクリーンアップされることではないと思います。
シミュレーターを使用してicloudからファイルを開くと、ファイルがapp-id-Inboxに存在し、クリーンアップ/削除されていないことがわかります。したがって、ファイルを読み取ろうとしたり、コピーしてファイルが存在しないというエラーが発生したりすると、ファイルがまだ存在していることがわかるため、セキュリティ上の問題だと思います。
DocumentPickerViewControllerのインポートモードで、これで解決します(申し訳ありませんが、c#コードを貼り付けますが、Swiftは目の前にあるため)
私が行ったNSUrlを返すDidPickDocumentメソッド内
NSData fileData = NSData.FromUrl(url);
これで、ファイルデータを含む「fileData」変数ができました。
次に、アプリの隔離されたスペースにある自分のカスタムフォルダーにそれらをコピーすると、問題なく動作します。
同じ問題が発生しましたが、表示されている一時ディレクトリを削除していたことが判明しました。したがって、表示されたときに保存してから削除し、documentPicker:didPickDocumentAtURLを適切に呼び出します。削除したファイルを指すのはURLだけです。
ドキュメントピッカーを使用している間、ファイルシステムを使用するには、以下のようにURLをファイルパスに変換します。
var filePath = urls[0].absoluteString
filePath = filePath.replacingOccurrences(of: "file:/", with: "")//making url to file path