iosswiftuidocument

Renaming UIDocument using UINavigationItemRenameDelegate


I'm trying to rename UIDocument open via document browser (UIDocumentBrowserViewController).

You can rename open documents in Apple Pages and Apple Numbers, and they use the usual UINavigationItemRenameDelegate controls.

Do these apps have special privileges for doing that? It appears that you can't use NSFileCoordinator or NSFileManager to achieve this, as the sandbox bookmark doesn't allow the file to be renamed or moved. The apps by Apple do this with no issue, though.

In my Info.plist I have:

UIFileSharingEnabled: true
LSSupportsOpeningDocumentsInPlace: true

And the following entitlements:

com.apple.security.read-write.user-selected: true
com.apple.security.files.user-selected.read-only: true
com.apple.security.files.bookmarks.app-scope: true

UIDocument documentation doesn't mention anything about sandboxing preventing renaming document files via UINavigationItemRenameDelegate.

https://developer.apple.com/documentation/uikit/uidocument

All the information online seems antiquated, except for this manual page, which doesn't really clarify my issues. Are the proprietary Apple apps using private methods / special entitlements to do this, or am I missing something here?

Update 2023-07-05:

WWDC session from 2022 has the following code snippet, and according to the talk, you should use UIDocumentWroserViewController to rename files:

extension ViewController: UINavigationItemRenameDelegate {
    func navigationItem(_ navigationItem: UINavigationItem, didEndRenamingWith title: String) {
        // Try renaming our document, the completion handler will have the updated URL or return an error.
        documentBrowserViewController.renameDocument(at: <#T##URL#>, proposedName: title, completionHandler: <#T##(URL?, Error?) -> Void#>)
    }
}

This is even more confusing. Should I instantiate a new document browser view controller? When doing that, I get the following error:

ERROR! Error Domain=NSCocoaErrorDomain Code=4099 "The connection from pid 3241 on anonymousListener or serviceListener was invalidated from this process." UserInfo={NSDebugDescription=The connection from pid 3241 on anonymousListener or serviceListener was invalidated from this process.}
[DocumentManager] The view service did terminate with error: Error Domain=_UIViewServiceErrorDomain Code=1 "(null)" UserInfo={Terminated=disconnect method}
[DocumentManager] The UIDocumentBrowserViewController view service crashed for too many times in a row.
[DocumentManager] The view service did terminate with error: Error Domain=_UIViewServiceErrorDomain Code=1 "(null)" UserInfo={Terminated=disconnect method}

Solution

  • After tons of trial and error, the answer was somewhat simple:

    let documentViewController = storyBoard.instantiateViewController(withIdentifier: "DocumentViewController") as! MyDocumentViewController
    documentViewController.document = MyDocument(fileURL: documentURL)
    documentViewController.documentBrowser = self
    

    Now, in your navigationItem:didEndRenamingWithTitle: you can call the document browser to rename your file.

    -(void)navigationItem:(UINavigationItem *)navigationItem didEndRenamingWithTitle:(NSString *)title
    {
        [self.documentBrowser renameDocumentAtURL:self.document.fileURL proposedName:title completionHandler:^(NSURL * _Nullable finalURL, NSError * _Nullable error) {
            if (error) {
                self.titleBar.title = self.document.fileURL.lastPathComponent.stringByDeletingPathExtension;
                return;
            }
            
            [self.document presentedItemDidMoveToURL:finalURL];
        }];
    }
    

    Swift:

    func navigationItem(_ navigationItem: UINavigationItem, didEndRenamingWith title: String) {
        self.documentBrowser.renameDocument(at: self.document.fileURL, to: title) { finalURL, error in
            if let error = error {
                self.titleBar.title = self.document.fileURL.lastPathComponent.deletingPathExtension
                return
            }
            
            self.document.presentedItemDidMove(to: finalURL)
        }
    }
    

    Apple should really include a minimal example in their documentation.