问题
The documentation for documentBrowser(_:didRequestDocumentCreationWithHandler:)
says, "Create a new document and save it to a temporary location. If you use a UIDocument
subclass to create the document, you must close it before calling the importHandler
block."
So I created a file URL by taking the URL for the user's temporary directory (FileManager.default.temporaryDirectory
) and appending a name and extension (getting a path like "file:///private/var/mobile/Containers/Data/Application/C1DE454D-EA1E-4166-B137-5B43185169D8/tmp/Untitled.uti"). But when I call save(to:for:completionHandler:)
passing this URL, the completion handler is never called back. I also tried using url(for:in:appropriateFor:create:)
to pass a subdirectory in the user's temporary directory—the completion handler was still never called.
I understand the document browser view controller is managed by a separate process, which has its own read / write permissions. Beyond that though, I'm having a hard time understanding what the problem is. Where can new documents be temporarily saved so that the document browser process can move them?
Update: as of the current betas, I now see an error with domain NSFileProviderInternalErrorDomain
and code 1
getting logged: "The reader is not permitted to access the URL." At least that's confirmation of what's happening…
回答1:
So, to start with, if you're using a custom UTI, it's got to be set up correctly. Mine look like this…
<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>
and
<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>
Also
<key>UISupportsDocumentBrowser</key>
<true/>
I subclass UIDocument
as MyDocument
and add the following method to create a new temp document…
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))
}
}
}
}
Then init and display the DBVC as follows:
@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
}
}
And my delegate methods are as follows:
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
}
}
}
And that's pretty much it. Let me know how you get on!
回答2:
I had the same issue, but then I realized that the recommended way was to simply copy the package/folder from the Bundle, like so:
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)
}
}
To clarify, this package is just a folder that you've created and plopped into Xcode.
This approach makes sense, if you think about it, for a few reasons:
Apple File System (AFS). The move towards AFS means copying is (almost) free.
Permissions. Copying from the Bundle is always permissible, and the user is specifying the location to copy to.
New document browser paradigm. Since we're using the new
UIDocumentBrowserViewController
paradigm (which is due to iOS11 and the new Files app), it is even handling the naming (c.f. Apple's Pages) and moving and arranging of files. We don't have to worry about which thread to run things on either.
So. Simpler, easier, and probably better. I can't think of a reason to manually create all the files (or use the temp folder, etc).
回答3:
Test on the device, not in the Simulator. The minute I switched to testing on the device, everything just started working correctly. (NOTE: It may be that the Simulator failures occur only for Sierra users like myself.)
来源:https://stackoverflow.com/questions/45341934/document-creation-with-uidocumentbrowserviewcontroller