Renaming a UIDocument in iCloud

I'm struggling to figure out how to rename an instance of a

UIDocument
subclass in an iCloud folder. I've tried saving the document with the new URL…


func renameDocument(to name: String) {


    let targetURL = document.fileURL.deletingLastPathComponent().appendingPathComponent(name)
        .appendingPathExtension("<extension>")


    document.save(to: targetURL, for: .forCreating) { success in
        guard success else {
            // This always fails
            return
        }
        // Success
    }
}


…but this fails with…

Error Domain=NSCocoaErrorDomain Code=513 "“<new-file-name>” couldn’t be moved because you don’t have permission to access “<folder>”."
UserInfo={NSSourceFilePathErrorKey=/private/var/mobile/Containers/Data/Application/1A9ACC2B-81EF-4EC9-940E-1C129BDB1914/tmp/(A Document Being Saved By My App)/<new-file-name>, NSUserStringVariant=( Move ), NSDestinationFilePath=/private/var/mobile/Library/Mobile Documents/com~apple~CloudDocs/<folder>/<new-file-name>, NSFilePath=/private/var/mobile/Containers/Data/Application/1A9ACC2B-81EF-4EC9-940E-1C129BDB1914/tmp/(A Document Being Saved By My App)/<new-file-name>, NSUnderlyingError=0x1c4e54280 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}

…and just a simple move…


func renameDocument(to name: String) {
    let targetURL = document.fileURL.deletingLastPathComponent().appendingPathComponent(name)
        .appendingPathExtension("<extension>")


    do {
        try FileManager.default.moveItem(at: document.fileURL, to: targetURL)
    } catch {
        // This always fails
    }       
    // Success
}



…which fails with…

Error Domain=NSCocoaErrorDomain Code=513 "“<old-file-name>” couldn’t be moved because you don’t have permission to access “<folder>”." UserInfo={NSSourceFilePathErrorKey=/private/var/mobile/Library/Mobile Documents/com~apple~CloudDocs/<folder>/<old-file-name>, NSUserStringVariant=( Move ), NSDestinationFilePath=/private/var/mobile/Library/Mobile Documents/com~apple~CloudDocs/<folder>/<new-file-name>, NSFilePath=/private/var/mobile/Library/Mobile Documents/com~apple~CloudDocs/<folder>/<old-file-name>, NSUnderlyingError=0x1c4c4d8c0 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}

Both of these work fine for local files, and renaming iCloud files works OK in the

UIDocumentBrowserViewController
root view controller. My guess is that there's some permission missing somewhere that allows the app to write to iCloud folders.


For info, the info.plist contains all the following keys…

  • LSSupportsOpeningDocumentsInPlace
  • NSExtensionFileProviderSupportsEnumeration
  • UISupportsDocumentBrowser
Post not yet marked as solved Up vote post of ash69 Down vote post of ash69
6.3k views

Replies

Hi,

I'm using this function and it works for local files and for files located in the iCloud. (it's part of my UIDocument subclass).


- (void)_moveURL:(NSURL*)sourceURL
         destURL:(NSURL*)destinationURL
      completion:(void (^)(NSError *))completionBlock
{
    NSParameterAssert(sourceURL);
    NSParameterAssert(destinationURL);
    NSParameterAssert(completionBlock);
   
    __block NSError *moveError = nil;
    __block BOOL moveSuccess = NO;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
        NSError *coordinatorError = nil;
        [coordinator coordinateWritingItemAtURL:sourceURL
                                        options:NSFileCoordinatorWritingForMoving
                               writingItemAtURL:destinationURL
                                        options:NSFileCoordinatorWritingForMoving
                                          error:&coordinatorError
                                     byAccessor:^(NSURL *newURL1, NSURL *newURL2)
         {
             NSFileManager *fileManager = [[NSFileManager alloc] init];
             moveSuccess = [fileManager moveItemAtURL:sourceURL toURL:destinationURL error:&moveError];
             if (moveSuccess)
             {
                 [self presentedItemDidMoveToURL:destinationURL];
             }
         }];
        if (moveSuccess)
        {
            completionBlock(nil);
            return;
        }
        if (moveError)
        {
            completionBlock(moveError);
            return;
        }
        if (coordinatorError)
        {
            completionBlock(coordinatorError);
            return;
        }
    });
}

I think when using UIDocumentBrowserViewController, your solution will not work. It might work if you specify the iCloud entitlement in Xcode for your app (which you don't need to do when using UIDocumentBrowserViewController. I believe when using UIDocumentBrowserViewController it uses a separate process and your app can't directly write access files that aren't in the app sandbox (files in iCloud drive are not in your sandbox) and is why @ash69 is getting the error. I'm seeing the same issue so right now I think it's not possible (at least without the iCloud entitlement). I'd love to be proven wrong as I'd also like to add this functionality to my app.

I believe you need to do something like the following to use a file URL for a file stored in iCloud:


if ( [fileUrl startAccessingSecurityScopedResource] ) {
     [self doSomethingWithURL:fileUrl];

}

using the above category method:


if ( myDocument.fileURL  startAccessingSecurityScopedResource] ) {
     [myDocument _moveURL:myDocument.fileURL destURL:moveToURL newURL completion:^(NSError* err) {
          if ( err == NULL ) {
               NSLog(@"moved URL!");
          }
    } ];
}


I didn't test this. You may need something additional depending on where moveToURL is located.

Thanks for providing this. I am still getting the 'operation not permitted' error on iCloud only. Something else is missing...

have to check ubiquityContainer exists every time FileManager.default.url(forUbiquityContainerIdentifier: containerIdentifier)?.appendingPathComponent("Documents") before you can access the documents folder.
  • Checking the ubiquity container was the key for me. Check the documentation for url(forUbiquityContainerIdentifier: containerIdentifier), and it lets you know it is required for permissions. That probably shouldn't be buried in that piece of documentation!

Add a Comment

Answer from Apple DTS:

The behavior and resulting limitations you describe are by design. This is because the destination path is outside of the app sandbox. You’ll have to ask the user to pick the destination.

If you believe an alternative approach should be considered by Apple, we encourage you to file an enhancement request with information on how this design decision impacts you, and what you’d like to see done differently.

Although there is no promise that the behavior will be changed, it is the best way to ensure your thoughts on the matter are seen by the team responsible for the decision.

While a Technical Support Incident (TSI) was initially debited from your Apple Developer Program account for this request, we have assigned a replacement incident back to your account.