5
votes

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
2
Is the document open when you are trying to rename it?theMikeSwan
It doesn't seem to make any difference whether it's open or closed.Ashley Mills
Have a look at developer.apple.com/documentation/foundation/filemanager/… see if that helps. I'm pretty sure some of the normal file manager methods don't work in the iCloud container (though iOS 11 may have changed things…)theMikeSwan
I'll take another look at that. I had a previous iOS 10 version that worked using that method, but I was under the impression that things had changed in iOS 11, since you can save a file to any location, in which case how do you know whether it's an iCloud or local folder… or some other location?Ashley Mills
This may be one of those spots Apple missed assuming that any renaming would happen in their shiny new document browser. I've experimented with the browser a little but not enough to have found the rough edges yet (other than the need to get the info.plist stuff exactly right for it to work).theMikeSwan

2 Answers

2
votes

Are you doing this in the context of NSFileCoordinator? This is required. You shouldn't need any info.plist settings apart from NSUbiquitousContainers.

Here is my code for renaming iCloud documents:

///
/// move cloudFile within same store - show errors
///
- (void)moveCloudFile:(NSURL *)url toUrl:(NSURL *)newURL
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
    ^{
        NSError *coordinationError;
        NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];

        [coordinator coordinateWritingItemAtURL:url    options:NSFileCoordinatorWritingForMoving
                               writingItemAtURL:newURL options:NSFileCoordinatorWritingForReplacing
                                                       error:&coordinationError
                                     byAccessor:
        ^(NSURL *readingURL, NSURL  *writingURL){
            if ([self moveFile:readingURL toUrl:writingURL])
                 [coordinator itemAtURL:readingURL didMoveToURL:writingURL];
        }];
        if (coordinationError) {
            MRLOG(@"Coordination error: %@", coordinationError);
            [(SSApplication *)SSApplication.sharedApplication fileErrorAlert:coordinationError];
        }
    });
}

///
///
/// move file within same store - show errors
///
- (BOOL)moveFile:(NSURL *)url toUrl:(NSURL *)newURL
{
    NSFileManager *manager = NSFileManager.defaultManager;
    NSError *error;

    if ([manager fileExistsAtPath:newURL.path])
        [self removeFile:newURL];

    if ([manager moveItemAtURL:url toURL:newURL error:&error] == NO) {
        MRLOG(@"Move failed: %@", error);
        [(SSApplication *)SSApplication.sharedApplication fileErrorAlert:error];
        return NO;
    }
    return YES;
}
1
votes

NSDestinationFilePath=/private/var/mobile/Library/Mobile Documents/com~apple~CloudDocs/<folder>/<new-file-name>

You're trying to save the document outside of your sandbox. You can only write files inside your own container, so there's no way to do what you want. Please file a bug at bugreport.apple.com if you want an API to rename the current file.