skip to Main Content

Goal: A user selects a folder in Files app where they would create files in that folder. This is an iOS app. This folder path is saved in the iOS app, using this app user would create file programmatically and save it in the app.

Issue: For some reason based on the below implementation, I can create file only onetime in that folder and any subsequent file creation leads to "Failed to access security scoped resource". To go around this, I need to select the folder again and then send it. THis is not the experience a user should face. A user should select the folder path only once and it is saved in iOS app, user should care about more about creating files in that folder.

Below is the code

class FolderManager: NSObject, UIDocumentPickerDelegate {
    
    // Singleton instance for shared access
    static let shared = FolderManager()
    
    private override init() {}
    
    // Function to present the document picker for selecting a folder
    func selectFolder(from viewController: UIViewController) {
        let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [UTType.folder], asCopy: false)
        documentPicker.delegate = self
        documentPicker.allowsMultipleSelection = false
        viewController.present(documentPicker, animated: true, completion: nil)
    }
    
    // Delegate method called when a folder is selected
    func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
        guard let selectedFolderURL = urls.first else { return }
        saveFolderURL(selectedFolderURL)
    }
    
    // Function to save the folder URL as bookmark data
    func saveFolderURL(_ url: URL) {
        do {
            let bookmarkData = try url.bookmarkData(options: .minimalBookmark, includingResourceValuesForKeys: nil, relativeTo: nil)
            UserDefaults.standard.set(bookmarkData, forKey: "selectedFolderURL")
        } catch {
            print("Error saving folder URL: (error)")
        }
    }

    func retrieveFolderURL() -> URL? {
            guard let bookmarkData = UserDefaults.standard.data(forKey: "selectedFolderURL") else { return nil }
            var isStale = false
            let folderURL = try? URL(resolvingBookmarkData: bookmarkData, options: [], bookmarkDataIsStale: &isStale)
            if isStale {
                print("Bookmark data is stale")
                // Handle stale bookmark data
                do {
                    let newBookmarkData = try folderURL?.bookmarkData(options: .minimalBookmark, includingResourceValuesForKeys: nil, relativeTo: nil)
                    UserDefaults.standard.set(newBookmarkData, forKey: "selectedFolderURL")
                } catch {
                    print("Error updating stale bookmark data: (error)")
                }
            }
            return folderURL
        }
    
    // Function to create and save a file in the selected folder
    func createFileInFolder(fileName: String, fileContents: String) {
        guard let folderURL = retrieveFolderURL() else {
            print("Failed to retrieve folder URL")
            return
        }
        
        if !folderURL.startAccessingSecurityScopedResource() {
            print("Failed to access security scoped resource")
            return
        }
        defer {
            folderURL.stopAccessingSecurityScopedResource()
            print("stoppedAccessingSecurityScopedResource")
        }
        
        let fileURL = folderURL.appendingPathComponent(fileName)
        
        do {
            try fileContents.write(to: fileURL, atomically: true, encoding: .utf8)
            print("File created successfully at (fileURL.path)")
        } catch {
            print("Error creating file: (error)")
        }
    }
}

I already reviewed some stackoverflow snippets based on that the code is already modified as per startAccessingSecurityScopedResource, stopAccessingSecurityScopedResource.

Any help is highly appreciated.

2

Answers


  1. To address the issue of "Failed to access security scoped resource" when creating files in a previously selected folder in your iOS app, you need to manage security-scoped URLs properly. When a user selects a folder, you should bookmark the URL to retain access between app launches.

    Login or Signup to reply.
  2. Make sure to pass the option withSecurityScope when creating your url bookmarkData.

    From the docs:

    "To create a security-scoped bookmark to support App Sandbox, include the withSecurityScope flag. When you later resolve the bookmark, you can use the resulting security-scoped URL to obtain read/write access to the file-system resource pointed to by the URL."

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search