Search code examples
macosswiftuiappstore-sandboxmac-catalystuidocumentpickerviewcontroller

UIDocumentPickerViewController configuration for Mac Catalyst


When using UIDocumentPickerViewController with Mac Catalyst, are additional capabilities, permissions or sandbox configuration required? Have I missed a caveat?

Running the following code on macOS (11.4) with Catalyst, the didPickDocumentsAt: delegate function is never called when a file is selected. However, documentPickerWasCancelled is called if the picker is dismissed or an attempt to open a file is made.

I initially suspected incorrect content types or delegate deallocation due to the SwiftUI's View struct recreation but as the as cancel is called, I suspect not.

A caveat identified is that asCopy must be false when configuring the UIDocumentPickerViewController for macOS but true for iOS.

The error indicates a Sandbox permission error:

DocumentPickerSwiftUI[42570:1472986] Failed to create an FPSandboxingURLWrapper for file:///Users/ed/test_file.txt. Error: Error Domain=NSPOSIXErrorDomain Code=1 "couldn't issue sandbox extension com.apple.app-sandbox.read-write for '/Users/ed/test_file.txt': Operation not permitted" UserInfo={NSDescription=couldn't issue sandbox extension com.apple.app-sandbox.read-write for '/Users/ed/test_file.txt': Operation not permitted}

Current capability configuration (read-only access of user-selected file):

Capabilities screenshot

import SwiftUI
import UIKit
import UniformTypeIdentifiers

struct ContentView: View {
    @State var showDocumentPicker: Bool = false

    var body: some View {
        documentPickerButton
    }

    private var documentPickerButton: some View {
        Button(action: {
            showDocumentPicker.toggle()
        }, label: {
            Text("Open")
        })
        .sheet(isPresented: self.$showDocumentPicker) {
            AudioFilePicker()
        }
    }
}

final class AudioFilePicker: UIViewControllerRepresentable {

    var viewController = UIDocumentPickerViewController(forOpeningContentTypes: [.item], asCopy: false)

    class Coordinator: NSObject, UIDocumentPickerDelegate {

        var parent: AudioFilePicker

        init(parent: AudioFilePicker) {
            self.parent = parent
        }

        func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
            print("Picker Loaded: \(String(describing: urls.first?.absoluteString))")
            // Do something with URL (never called)
        }

        func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
            print("Picker Cancelled")
        }
    }

    func makeCoordinator() -> Coordinator {
        return Coordinator(parent: self)
    }

    func makeUIViewController(context: Context) -> UIDocumentPickerViewController {
        viewController.delegate = context.coordinator
        return viewController
    }

    func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) { }
}

Solution

  • the error does indicate a permission error. Try adding:

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