iOS Creating a file in a public folder

Hello there. I want to create a file in a public folder (where it could be seen by user, and the file could be read again.

To achieve that, I use document picker, and set the document type to "public.folder" and inMode "UIDocumentPickerModeOpen".

After user opens the document picker and selects the desired folder, in "didPickDocumentsAtURLs" callback I get the NSUrl object, which has permissions to modify the file at that url (in this case, it's an url to a folder).

There is my issue. I have the url with access permission to a folder, however, to create a file I ussualy need to have the filename.extension in the url. If I were to modify the NSUrl object I've received from the document picker, or convert it to NSString, my guess is I lose the access permission and "createFileAtPath" method always fails.

Any leads would be kindly appreciated!

If your process is granted a sandbox extension to modify a folder then that extension covers any items within that folder. So, you should be able to do something like this:

let dir: URL = …
let file = dir.appendingPathComponent("test.txt")
let success = dir.startAccessingSecurityScopedResource()
guard success else { … }
defer { dir.stopAccessingSecurityScopedResource() }
let content = Data("Hello Cruel World!".utf8)
try content.write(to: file, options: [])

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Here's the code:

NSError *error = nil;
NSString* data = @"This data going into the file";

NSURL *destURLPath = [NSURL URLWithString:urls[0].absoluteString];
NSURL *destURLPath2 = [destURLPath URLByAppendingPathComponent:@"testText.txt"];

[destURLPath2 startAccessingSecurityScopedResource]; //Let the os know we're going to read the file

//destURLPath2 = file:///private/var/mobile/Containers/Shared/AppGroup/C9D2B2AF-4086-45BF-98C6-B0BEBA599FA4/File%20Provider%20Storage/New/testText.txt

if( [data writeToURL:destURLPath2 atomically:true encoding:NSUTF8StringEncoding error:&error] == NO )
    NSLog(@"Error: %@ %@", error, [error userInfo]);

[destURLPath2 stopAccessingSecurityScopedResource];

Yes indeed, your code in swift works like a charm!

Cool.

However, I didn't mention before, but my project is in objective-c.

There’s nothing Swift specific about the techniques I’m describing; they should all work just fine in Objective-C.

As to what’s going on here, I see that you’re round tripping your URLs through NSString. This is likely to cause problems because a security-scoped URL contains extra info, the security scope itself, that’s easily dropped during that round trip. I recommend that you tweak your code to use URLs everywhere.

Also, check the function result from -startAccessingSecurityScopedResource. I suspect you’ll find that it’s returning NO, which is a clear indication of this problem. However, you must check this result in production code too; just because the security scope in the URL was once valid does not mean that it’ll be valid forever.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Accepted Answer

Hello eskimo!

Thank you for the advices, they are super helpful and I'll be sure to implement them in my code!

However, I won't accept your answer, since the issue in my case was, that I was trying to call startAccessingSecurityScopedResource with a modified url (the folder that the user chose + NewFileName.extension). It kept returning false, no matter how I've modified the URL.

What worked for me, was calling startAccessingSecurityScopedResource with an unmodified folder url, that I receive from document picker.

Here I will leave a fully working example for future readers, to make their life easier:

- (void)openDocumentPicker
{
    //This is needed, when using this code on QT!
    //Find the current app window, and its view controller object
    /*
    UIApplication* app = [UIApplication sharedApplication];
    UIWindow* rootWindow = app.windows[0];
    UIViewController* rootViewController = rootWindow.rootViewController;
     */
    
    //Initialize the document picker. Set appropriate document types
    //When reading: use document type of the file, that you're going to read
    //When writing into a new file: use @"public.folder" to select a folder, where your new file will be created
    UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.folder"] inMode:UIDocumentPickerModeOpen];
    
    //Assigning the delegate, connects the document picker object with callbacks, defined in this object
    documentPicker.delegate = self;

    documentPicker.modalPresentationStyle = UIModalPresentationFormSheet;

    //In this case we're using self. If using on QT, use the rootViewController we've found before
    [self presentViewController:documentPicker animated:YES completion:nil];
}

- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls
{
    //If we come here, user successfully picked a single file/folder
    
    //When selecting a folder, we need to start accessing the folder itself, instead of the specific file we're going to create
    if ( [urls[0] startAccessingSecurityScopedResource] ) //Let the os know we're going use this resource
    {
        //Write file case ---
        
        //Construct the url, that we're going to be using: folder the user chose + add the new FileName.extension
        NSURL *destURLPath = [urls[0] URLByAppendingPathComponent:@"Test.txt"];
        
        NSString *dataToWrite = @"This text is going into the file!";
        
        NSError *error = nil;
        
        //Write the data, thus creating a new file. Save the new path if operation succeeds
        if( ![dataToWrite writeToURL:destURLPath atomically:true encoding:NSUTF8StringEncoding error:&error] )
            NSLog(@"%@",[error localizedDescription]);

        
        //Read file case ---
        NSData *fileData = [NSData dataWithContentsOfURL:destURLPath options:NSDataReadingUncached error:&error];
        
        if( fileData == nil )
            NSLog(@"%@",[error localizedDescription]);
        
        [urls[0] stopAccessingSecurityScopedResource];
    }
    else
    {
        NSLog(@"startAccessingSecurityScopedResource failed");
    }
}
iOS Creating a file in a public folder
 
 
Q