【问题标题】:Document Creation with UIDocumentBrowserViewController使用 UIDocumentBrowserViewController 创建文档
【发布时间】:2018-01-02 15:51:11
【问题描述】:

documentBrowser(_:didRequestDocumentCreationWithHandler:) 的文档说:“创建一个新文档并将其保存到临时位置。如果您使用 UIDocument 子类创建文档,则必须在调用 importHandler 块之前关闭它。”

因此,我通过获取用户临时目录 (FileManager.default.temporaryDirectory) 的 URL 并附加名称和扩展名(获取类似“file:///private/var/mobile/Containers/Data/应用程序/C1DE454D-EA1E-4166-B137-5B43185169D8/tmp/Untitled.uti")。但是当我通过这个 URL 调用 save(to:for:completionHandler:) 时,完成处理程序永远不会被回调。我还尝试使用url(for:in:appropriateFor:create:) 传递用户临时目录中的子目录——完成处理程序仍然没有被调用。

我了解文档浏览器视图控制器是由一个单独的进程管理的,它有自己的读/写权限。除此之外,我很难理解问题所在。新文档可以临时保存在哪里,以便文档浏览器进程可以移动它们?

更新:在当前的测试版中,我现在看到域 NSFileProviderInternalErrorDomain 和代码 1 被记录的错误:“不允许读者访问 URL。”至少这是对正在发生的事情的确认……

【问题讨论】:

  • 有同样的问题,但最终得到了这个工作。您使用的是自定义 UTI 吗?
  • 是的,我有一个导出的 UTI,它也用作文档类型。您是否发现您必须做的事情与 UTI 文档的建议有所不同?
  • 您是否创建了临时目录? IIRC,默认情况下 FileManager.temporaryDirectory 返回尚未创建的目录的 URL。您需要创建它。另外,你为什么使用 save(to:) 而不是 close(completionHandler:)?你是如何创建你的 UIDocument 的?

标签: ios nsfilemanager ios11 uidocument uidocumentbrowservc


【解决方案1】:

因此,首先,如果您使用的是自定义 UTI,则必须正确设置它。我的长这样……

<key>CFBundleDocumentTypes</key>
<array>
    <dict>
        <key>CFBundleTypeIconFiles</key>
        <array>
            <string>icon-file-name</string> // Can be excluded, but keep the array
        </array>
        <key>CFBundleTypeName</key>
        <string>Your Document Name</string>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>LSHandlerRank</key>
        <string>Owner</string>
        <key>LSItemContentTypes</key>
        <array>
            <string>com.custom-uti</string>
        </array>
    </dict>
</array>

<key>UTExportedTypeDeclarations</key>
<array>
    <dict>
        <key>UTTypeConformsTo</key>
        <array>
            <string>public.data</string>  // My doc is saved as Data, not a file wrapper
        </array>
        <key>UTTypeDescription</key>
        <string>Your Document Name</string>
        <key>UTTypeIdentifier</key>
        <string>com.custom-uti</string>
        <key>UTTypeTagSpecification</key>
        <dict>
            <key>public.filename-extension</key>
            <array>
                <string>doc-extension</string>
            </array>
        </dict>
    </dict>
</array>

还有

<key>UISupportsDocumentBrowser</key>
<true/>

我将UIDocument 子类化为MyDocument 并添加以下方法来创建一个新的临时文档...

static func create(completion: @escaping Result<MyDocument> -> Void) throws {

    let targetURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("Untitled").appendingPathExtension("doc-extension")

    coordinationQueue.async {
        let document = MyDocument(fileURL: targetURL)
        var error: NSError? = nil
        NSFileCoordinator(filePresenter: nil).coordinate(writingItemAt: targetURL, error: &error) { url in
            document.save(to: url, for: .forCreating) { success in
                DispatchQueue.main.async {
                    if success {
                        completion(.success(document))
                    } else {
                        completion(.failure(MyDocumentError.unableToSaveDocument))
                    }
                }
            }
        }
        if let error = error {
            DispatchQueue.main.async {
                completion(.failure(error))
            }
        }
    }
}

然后初始化并显示 DBVC,如下所示:

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    lazy var documentBrowser: UIDocumentBrowserViewController = {
        let utiDecs = Bundle.main.object(forInfoDictionaryKey: kUTExportedTypeDeclarationsKey as String) as! [[String: Any]]
        let uti = utiDecs.first?[kUTTypeIdentifierKey as String] as! String
        let dbvc = UIDocumentBrowserViewController(forOpeningFilesWithContentTypes:[uti])

        dbvc.delegate = self
        return dbvc
    }()

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        window = UIWindow(frame: UIScreen.main.bounds)
        window?.rootViewController = documentBrowser
        window?.makeKeyAndVisible()

        return true
    }
}

而我的委托方法如下:

func documentBrowser(_ controller: UIDocumentBrowserViewController, didRequestDocumentCreationWithHandler importHandler:    @escaping (URL?, UIDocumentBrowserViewController.ImportMode) -> Swift.Void) {

    do {
        try MyDocument.create() { result in
            switch result {
            case let .success(document):
                // .move as I'm moving a temp file, if you're using a template
                // this will be .copy 
                importHandler(document.fileURL, .move) 
            case let .failure(error):
                // Show error
                importHandler(nil, .none)
            }
        }
    } catch {
        // Show error
        importHandler(nil, .none)
    }
}

func documentBrowser(_ controller: UIDocumentBrowserViewController, didImportDocumentAt sourceURL: URL, toDestinationURL destinationURL: URL) {
    let document = MyDocument(fileURL: destinationURL)
    document.open { success in
        if success {
            // Modally present DocumentViewContoller for document
        } else {
            // Show error
        }
    }
}

差不多就是这样。告诉我你过得怎么样!

【讨论】:

  • 您的 UTI 定义与我的配置完全匹配,除了我使用 FileWrapper 实例来读取和写入数据,因此我的 UTI 符合 com.apple.package。我在您的方法实现中看到的唯一区别是 create(completion:) 助手,它使用单独的文件协调器来编写文件。我尝试将其添加到我的实现中,但没有发现任何区别。据我所知,UIDocument 的子类没有必要将它们的文件操作包装在单独的协调器中,因为UIDocument 采用了NSFilePresenter
  • 你是否在 info.plist 中设置了UISupportsDocumentBrowser = true
  • 是的,就在里面。
  • 使用UIDocumentBrowserViewController作为你的根视图控制器?
  • 是的。我使用 Xcode 的基于文档的应用程序模板作为我的起点,并没有偏离它组织项目的方式。
【解决方案2】:

我遇到了同样的问题,但后来我意识到推荐的方法是简单地从 Bundle 中复制包/文件夹,如下所示:

func documentBrowser(_ controller: UIDocumentBrowserViewController, didRequestDocumentCreationWithHandler importHandler: @escaping (URL?, UIDocumentBrowserViewController.ImportMode) -> Void) {
    if let url = Bundle.main.url(forResource: "Your Already Created Package", withExtension: "your-package-extension") {
        importHandler(url, .copy)
    } else {
        importHandler(nil, .none)
    }
}

为了澄清,这个包只是你创建并放入 Xcode 中的一个文件夹。

如果您考虑一下,这种方法很有意义,原因如下:

  1. Apple 文件系统 (AFS)。转向 AFS 意味着复制(几乎)是免费的。

  2. 权限。从 Bundle 复制始终是允许的,并且用户正在指定要复制到的位置。

  3. 新的文档浏览器范例。由于我们使用的是新的UIDocumentBrowserViewController 范例(这是由于 iOS11 和新的文件应用程序),它甚至可以处理文件的命名(参见 Apple 的页面)以及移动和排列文件。我们也不必担心在哪个线程上运行。

所以。更简单,更容易,并且可能更好。我想不出手动创建所有文件(或使用临时文件夹等)的理由。

【讨论】:

  • 是的,您当然可以这样做。如果你实现了一个模板选择器,那么从主包中复制将是一个明确的方法。您关于在 APFS 中利用克隆的观点很好。我很想听听其他人认为应该优先考虑的内容。
  • 认为使用这种方法的文档创建日期会不正确 - 即它不会在复制过程中更新,并且会使用您捆绑的模板文档的创建日期。
【解决方案3】:

在设备上测试,而不是在模拟器中。在我切换到设备测试的那一刻,一切都开始正常工作。 (注意:模拟器故障可能只发生在像我这样的 Sierra 用户身上。)

【讨论】:

  • 我一直......我几乎没有使用模拟器来测试这个。您的回答是否意味着,在您的情况下,文档浏览器视图控制器进程可以访问用户的临时目录?还是您使用了其他目录?
  • @ErikFoss 我使用了临时目录。工作得很好。我的代码就像文档中显示的示例代码:developer.apple.com/documentation/uikit/… 当我切换到在设备上进行测试时,“不允许阅读器访问 URL”错误消失了,文档在创建时和之后变得可打开.
  • 感谢您的确认——我已经得出结论(无论出于何种原因)文档浏览器视图控制器进程没有有权访问该目录。阅读您的评论后,我决定(一时兴起)尝试删除并重新创建我的应用程序的 iCloud 凭据……现在看来一切正常。 ?
  • @ErikFoss 太好了!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-22
  • 2020-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-05
相关资源
最近更新 更多