swiftmacosswiftui

FileManager enumerator's nextObject nil for 'user selected' in Sandbox


Using SwiftUI's .fileImporter to get a 'user selected' URL for a directory, when using FileManager's URL enumerator, nextObject() returns nil.

Entitlement:

<key>com.apple.security.files.user-selected.read-only</key>
<true/>

(Tried read-write, same result)

From SwiftUI view

.fileImporter(
    isPresented: $showingOpenPanel,
    allowedContentTypes: [.fileURL, .directory],
    onCompletion: { result in
        viewModel.onImport(result: result)
    }
)

Getting valid URL (targetURL), but cannot get any file from enumerator:

// In ViewModel
switch result {
case .success(let url):
    targetURL = url // URL is valid ("/Users/<username>/Desktop/MyFolder/")
...

let enumerator = FileManager.default.enumerator(at: targetURL, includingPropertiesForKeys: nil) // enumerator is not nil
enumerator?.nextObject() // nil

Removing Sandbox entitlement resolve the issue (so it's Sandbox related). But is not user-selected means that user pick from the OpenPanel using fileImporter?


Solution

  • According to the documentation, fileImporter gives you security-scoped URLs, so you will need to call startAccessingSecurityScopedResource and stopAccessingSecurityScopedResource to access them.

    e.g.

    if targetURL.startAccessingSecurityScopedResource() {
        defer { targetURL.stopAccessingSecurityScopedResource() }
    
        let enumerator = FileManager.default.enumerator(at: targetURL, includingPropertiesForKeys: nil)
    
        // do things with the enumerator...
        print(enumerator?.nextObject())
    } else {
        // fail to access the URL, handle this appropriately
    }
    

    You should also create a bookmark for the URL if you need to access it at a later date. See bookmarkData and init(resolvingBookmarkData:).

    For more info, see the sections about security scopes in the NSURL documentation page.