Как получить доступ к файлам в iCloud Drive из моего приложения для iOS?
Есть ли способ выбрать файл из iCloud Drive аналогичным способом UIImagePickerController()
?
9 ответов
Вам нужно включить iCloud
прав. Как только вы это сделаете, вы сможете представить контроллер следующим образом:
let documentPickerController = UIDocumentPickerViewController(documentTypes: [String(kUTTypePDF), String(kUTTypeImage), String(kUTTypeMovie), String(kUTTypeVideo), String(kUTTypePlainText), String(kUTTypeMP3)], inMode: .Import)
documentPickerController.delegate = self
presentViewController(documentPickerController, animated: true, completion: nil)
В вашем делегате реализуйте метод:
func documentPicker(controller: UIDocumentPickerViewController, didPickDocumentAtURL url: NSURL)
Swift 5, iOS 13
Ответы Джонаттана и Ашу определенно находятся на правильном пути для основных функций, существует ряд проблем с выбором нескольких документов, результатами ошибок и устаревшим API-интерфейсом выбора документов.
В приведенном ниже коде показана современная сквозная версия распространенного варианта использования: выберите внешний документ iCloud для импорта в приложение и сделайте с ним что-нибудь.
Обратите внимание, что у вас должны быть настроены возможности вашего приложения для использования документов iCloud и настроен контейнер повсеместности в.plist вашего приложения... См., Например: Быстрая запись / сохранение / перемещение файла документа на диск iCloud
class ViewController: UIViewController {
@IBAction func askForDocument(_ sender: Any) {
if FileManager.default.url(forUbiquityContainerIdentifier: nil) != nil {
let iOSPickerUI = UIDocumentPickerViewController(documentTypes: ["public.text"], in: .import)
iOSPickerUI.delegate = self
iOSPickerUI.modalPresentationStyle = .formSheet
if let popoverPresentationController = iOSPickerUI.popoverPresentationController {
popoverPresentationController.sourceView = sender as? UIView
}
self.present(iOSPickerUI, animated: true, completion: nil)
}
}
func processImportedFileAt(fileURL: URL) {
// ...
}
}
extension ViewController: UIDocumentPickerDelegate, UINavigationControllerDelegate {
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
dismiss(animated: true, completion: nil)
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
if controller.allowsMultipleSelection {
print("WARNING: controller allows multiple file selection, but coordinate-read code here assumes only one file chosen")
// If this is intentional, you need to modify the code below to do coordinator.coordinate
// on MULTIPLE items, not just the first one
if urls.count > 0 { print("Ignoring all but the first chosen file") }
}
let firstFileURL = urls[0]
let isSecuredURL = (firstFileURL.startAccessingSecurityScopedResource() == true)
print("UIDocumentPickerViewController gave url = \(firstFileURL)")
// Status monitoring for the coordinate block's outcome
var blockSuccess = false
var outputFileURL: URL? = nil
// Execute (synchronously, inline) a block of code that will copy the chosen file
// using iOS-coordinated read to cooperate on access to a file we do not own:
let coordinator = NSFileCoordinator()
var error: NSError? = nil
coordinator.coordinate(readingItemAt: firstFileURL, options: [], error: &error) { (externalFileURL) -> Void in
// WARNING: use 'externalFileURL in this block, NOT 'firstFileURL' even though they are usually the same.
// They can be different depending on coordinator .options [] specified!
// Create file URL to temp copy of file we will create:
var tempURL = URL(fileURLWithPath: NSTemporaryDirectory())
tempURL.appendPathComponent(externalFileURL.lastPathComponent)
print("Will attempt to copy file to tempURL = \(tempURL)")
// Attempt copy
do {
// If file with same name exists remove it (replace file with new one)
if FileManager.default.fileExists(atPath: tempURL.path) {
print("Deleting existing file at: \(tempURL.path) ")
try FileManager.default.removeItem(atPath: tempURL.path)
}
// Move file from app_id-Inbox to tmp/filename
print("Attempting move file to: \(tempURL.path) ")
try FileManager.default.moveItem(atPath: externalFileURL.path, toPath: tempURL.path)
blockSuccess = true
outputFileURL = tempURL
}
catch {
print("File operation error: " + error.localizedDescription)
blockSuccess = false
}
}
navigationController?.dismiss(animated: true, completion: nil)
if error != nil {
print("NSFileCoordinator() generated error while preparing, and block was never executed")
return
}
if !blockSuccess {
print("Block executed but an error was encountered while performing file operations")
return
}
print("Output URL : \(String(describing: outputFileURL))")
if (isSecuredURL) {
firstFileURL.stopAccessingSecurityScopedResource()
}
if let out = outputFileURL {
processImportedFileAt(fileURL: out)
}
}
}
Это снова изменилось в iOS 14!!
Рабочий пример для JSON:
import UIKit
import MobileCoreServices
import UniformTypeIdentifiers
func selectFiles() {
let types = UTType.types(tag: "json",
tagClass: UTTagClass.filenameExtension,
conformingTo: nil)
let documentPickerController = UIDocumentPickerViewController(
forOpeningContentTypes: types)
documentPickerController.delegate = self
self.present(documentPickerController, animated: true, completion: nil)
}
Swift 4.X
Вам нужно включить iCloud
права в возможностях XCode. Также вы должны включить iCloud
в комплекте приложений в учетной записи разработчика Apple. После этого вы можете представить контроллер средства выбора документов следующим образом:
использование UIDocumentPickerDelegate
методы
extension YourViewController : UIDocumentMenuDelegate, UIDocumentPickerDelegate,UINavigationControllerDelegate {
func documentMenu(_ documentMenu: UIDocumentMenuViewController, didPickDocumentPicker documentPicker: UIDocumentPickerViewController) {
documentPicker.delegate = self
self.present(documentPicker, animated: true, completion: nil)
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) {
print("url = \(url)")
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
dismiss(animated: true, completion: nil)
}
}
Добавьте ниже код для кнопки действий
@IBAction func didPressAttachment(_ sender: UIButton) {
let importMenu = UIDocumentMenuViewController(documentTypes: [String(kUTTypePDF)], in: .import)
importMenu.delegate = self
importMenu.modalPresentationStyle = .formSheet
if let popoverPresentationController = importMenu.popoverPresentationController {
popoverPresentationController.sourceView = sender
// popoverPresentationController.sourceRect = sender.bounds
}
self.present(importMenu, animated: true, completion: nil)
}
Это работает нормально для меня. Надеюсь, это вам тоже поможет.
Удачного кодирования:)
Средство выбора документов вызывает делегат documentPicker:didPickDocumentAtURL: метод, когда пользователь выбирает место назначения вне песочницы вашего приложения. Система сохраняет копию вашего документа в указанном месте назначения. Средство выбора документов предоставляет URL-адрес копии, чтобы указать успех; однако ваше приложение не имеет доступа к файлу, на который ссылается этот URL. Ссылка на сайт
Этот код работает для меня:
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
let url = urls[0]
let isSecuredURL = url.startAccessingSecurityScopedResource() == true
let coordinator = NSFileCoordinator()
var error: NSError? = nil
coordinator.coordinate(readingItemAt: url, options: [], error: &error) { (url) -> Void in
_ = 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)
YourFunction(tempURL)
return tempURL
} catch {
print(error.localizedDescription)
return nil
}
}
}
if (isSecuredURL) {
url.stopAccessingSecurityScopedResource()
}
navigationController?.dismiss(animated: true, completion: nil)
}
I CloudUrl.startAccessingSecurityScopedResource() // на данный момент возвращает true для меня,
Однако следующий код дал ошибку:
попробуйте FileManager.default.createDirectory(atPath: iCloudUrl, withIntermediateDirectories: true, attributes: nil)
"Вы не можете сохранить файл" xyz ", потому что том доступен только для чтения".
Это действительно работает:
попробуйте FileManager.default.createDirectory(по адресу: iCloudUrl, withIntermediateDirectories: true, attributes: nil)
Это имеет смысл, потому что URL-адрес, вероятно, несет в себе безопасный доступ, но эта небольшая оплошность поставила меня в тупик на полдня...
Для моих пользователей swiftUI: это довольно просто.
struct HomeView: View {
@State private var showActionSheet = false
var body: some View {
ButtonComponentView(title: "Press", handler: {
showActionSheet = true
})
.fileImporter(isPresented: $showActionSheet, allowedContentTypes: [.data]) { (res) in
print("!!!\(res)")
}
}
}
Вместо перемещения/удаления файлов и манипулирования ими, Apple предлагает готовое решение, если мы просто выполним соответствующие шаги: вот фрагмент кода, который открывает URL-адрес iCloud.
Это решение включает в себя:
- Начало доступа к ресурсу, защищенному областью безопасности.
- Использование координации файлов для безопасного доступа к файлам iCloud.
- Детальная обработка ошибок для эффективной диагностики проблем.
Фрагмент кода:
func handleICloudFile(at url: URL) -> ConcreteFile? {
guard url.startAccessingSecurityScopedResource() else {
print("Failed to access security-scoped resource.")
return nil
}
defer { url.stopAccessingSecurityScopedResource() }
var error: NSError?
var concreteFile: ConcreteFile?
NSFileCoordinator().coordinate(readingItemAt: url, options: [], error: &error) { (newURL) in
do {
let data = try Data(contentsOf: newURL)
} catch {
print("Error: \(error.localizedDescription)")
}
}
if let error = error {
print("File coordination error: \(error)")
}
return concreteFile
}
я использую
try FileManager.default.copyItem(at: url, to: destinationUrl)
вместо MoveItem . В противном случае файлы удаляются из iCloud Drive, чего я не хочу.