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…
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With