iOS Creating a file in a public folder

Viewed 404

I want to let the user choose the folder, where I'd save a new file.

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.

What method do I need to use, or what configuration document picker do I need, in order to create a new file in the path that the user selected? I attach my current code:

- (void)openDocumentPicker:(NSString*)pickerType
{
    //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
    UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[pickerType] inMode:UIDocumentPickerModeOpen];

    //Assigning the delegate, connects the document picker object with callbacks, defined in this object
    documentPicker.delegate = self;

    documentPicker.modalPresentationStyle = UIModalPresentationFormSheet;

    //Call the document picker, to the view controller that we've found before
    [rootViewController presentViewController:documentPicker animated:YES completion:nil];
}


- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls
{
    //If we come here, user successfully picked a file/folder

    [urls[0] startAccessingSecurityScopedResource]; //Let the os know we're going to use the file
        
    NSFileManager *fileManager = [NSFileManager defaultManager];

    NSString *documentsDirectory = urls[0].absoluteString;

    NSString *newFilePath = [documentsDirectory stringByAppendingPathComponent:@"test.txt"];
    NSError *error = nil;
        
    if ([fileManager createFileAtPath:newFilePath contents:[@"new file test" dataUsingEncoding:NSUTF8StringEncoding] attributes:nil]){
        NSLog(@"Create Sucess");
    }
    else{
        NSLog(@"Create error: %@", error);
    }

    [urls[0] stopAccessingSecurityScopedResource]; //Let the os know we're done
}

Any leads would be kindly appreciated!

2 Answers

This is solution in swift, please try and let me knwo if any problem

func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]){
    
    var imgData: Data?
    if let url = urls.first{
        imgData = try? Data(contentsOf: url)
        do{
            let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString
            let destURLPath = documentDirectory.appendingPathComponent(url.lastPathComponent)
            try imgData?.write(to: URL(fileURLWithPath: destURLPath))
            print("FILES IS Writtern at DOcument Directory")
        }catch{
            
        }
        
        
    }
    
}

To answer my own question, I'll leave a fully working code bellow.

My main issue was, that when you're using "public.folder" document type, you need to call startAccessingSecurityScopedResource with the url of the chosen folder, not with the modified link (file the user chose + NewFileName.extension)

- (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");
    }
}

This was also being discussed at apple forums:

Thread name: "iOS Creating a file in a public folder"

Thread link: https://developer.apple.com/forums/thread/685170?answerId=682427022#682427022

Related