Also, to be clear, using "withoutImplicitStartAccessing" is EXCEEDINGLY rare, even within the system. I can't think of any reason why an app would do this, as you're essentially asking the system to give you a URL you can't use.
There must be some reason... otherwise we wouldn't have withoutImplicitStartAccessing in the first place ;)
FWIW iOS UIDocumentPickerViewController provides you an URL for with startAccessingSecurityScopedResource has not been called yet.. I guess this is another difference between the two platforms.
Please file a bug
Will do shortly.
returning the app name has privacy implications
But so should be the case on macOS, no?
import SwiftUI
struct ContentView: View {
var body: some View {
let view = Button("Pick Folder") {
#if os(macOS)
macPickFolder()
#else
phoneShowPicker = true
#endif
}
.buttonStyle(.borderedProminent)
#if os(macOS)
return view
#else
return view.sheet(isPresented: $phoneShowPicker) {
PhoneFilePicker(isFolder: true) { url in
if let url {
printUrl(url)
}
}
}
#endif
}
#if os(macOS)
func macPickFolder() {
let panel = NSOpenPanel()
panel.directoryURL = URL.libraryDirectory.appendingPathComponent("Containers")
panel.allowsMultipleSelection = false
panel.canChooseDirectories = true
panel.canChooseFiles = true
panel.allowedContentTypes = [.folder]
if panel.runModal() == .OK {
if let url = panel.url {
printUrl(url)
}
}
}
#else
@State private var phoneShowPicker = false
#endif
func printUrl(_ url: URL) {
// NOTE: on mac it's possible to select the app container itself
// and on iOS it is not, you could only select the "Documents" folder inside
// making the test consistent, so that even on macOS if you select "Data"
// inside the app container it will work the same way as on iOS.
var url = url
if (url.lastPathComponent == "Data") || (url.lastPathComponent == "Documents") {
url = url.deletingLastPathComponent()
}
let result = url.startAccessingSecurityScopedResource()
print("startAccessingSecurityScopedResource result:", result)
defer { url.stopAccessingSecurityScopedResource() }
let values = try! url.resourceValues(forKeys: [.nameKey, .localizedNameKey])
print("url:", url)
print("name:", values.name ?? "nil")
print("localizedName:", values.localizedName ?? "nil")
}
}
#Preview {
ContentView()
}
#if !os(macOS)
struct PhoneFilePicker: UIViewControllerRepresentable {
let isFolder: Bool
var onPick: (URL?) -> Void
func makeUIViewController(context: Context) -> UIDocumentPickerViewController {
let picker = UIDocumentPickerViewController(forOpeningContentTypes: [isFolder ? .folder : .item], asCopy: false)
picker.delegate = context.coordinator
picker.allowsMultipleSelection = false
return picker
}
func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(onPick: onPick)
}
class Coordinator: NSObject, UIDocumentPickerDelegate {
var onPick: (URL?) -> Void
init(onPick: @escaping (URL?) -> Void) {
self.onPick = onPick
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
onPick(urls.first!)
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
onPick(nil)
}
}
}
#endif
@main struct TestApp: App {
var body: some Scene {
WindowGroup { ContentView() }
}
}
Example output on macOS:
url: file:///Users/user/Library/Containers/com.apple.AboutThisMacLauncher/
name: com.apple.AboutThisMacLauncher
localizedName: About This Mac
on iOS:
url: file:///private/var/mobile/Containers/Data/Application/2A6A7975-6CE1-40F0-B7D0-18A6E64C0D89/
name: 2A6A7975-6CE1-40F0-B7D0-18A6E64C0D89
localizedName: 2A6A7975-6CE1-40F0-B7D0-18A6E64C0D89
Are you starting from a document? You should be able to get the UTI of a document. If you ask for the localizedDescription, that might be what you're looking for.
Etresoft, I am not sure I follow, please explain.
PS. I'm not sure if this is important or not: compared to mac where I could select the container folder itself on iPhone I could only select the Documents folder inside... - the "File" UI just works that way... Which means I have to deleteLastPathComponent to get to the app URL... but that URL was not exactly the one user gave permission for.. Maybe because of that localizedName doesn't work? (despite it works on Mac after stripping the trailing "Data" off the url, but at this point I'd be not surprised if that's yet another difference between the platforms).