适配App Store:macOS沙箱环境下URL内容访问权限问题求解
macOS沙箱环境下SwiftUI读取文件夹权限问题解决
问题场景
使用SwiftUI的fileImporter选择文件夹后,尝试遍历其内容时在macOS触发权限错误,代码在iOS上可正常运行,但因应用需上架App Store无法关闭沙箱,报错信息如下:
count: 1 - - - - - - - - - - - /Volumes/mac Drive/My Releases Item is a folder! Name: My Releases The file “My Releases” couldn’t be opened because you don’t have permission to view it. - - - - - - - - - - -
问题原因
macOS沙箱机制下,通过文件选择器获取的URL默认不具备临时访问权限,必须主动调用startAccessingSecurityScopedResource()申请权限,访问完成后需调用stopAccessingSecurityScopedResource()释放权限,否则会被沙箱拦截操作。
解决方法
修改代码,确保在访问文件选择器返回的URL及其子内容时,先启动安全范围资源访问,所有文件操作都在授权有效期内执行。
修改后的完整代码
import SwiftUI struct ContentView: View { @State var isShowing: Bool = false var body: some View { VStack { Button("Import Folder") { isShowing.toggle() } } .padding() .fileImporter(isPresented: $isShowing, allowedContentTypes: [.folder], allowsMultipleSelection: false, onCompletion: { results in switch results { case .success(let items): print("count: \(items.count)") print("- - - - - - - - - - -") for item in items { print(item.path) // 申请临时访问权限,失败则跳过 guard item.startAccessingSecurityScopedResource() else { print("无法获取文件夹访问权限") continue } // 确保函数退出时自动释放权限 defer { item.stopAccessingSecurityScopedResource() } lookToURL(item: item) } print("- - - - - - - - - - -") case .failure(let error): print(error) } }) } } func lookToURL(item: URL) { isItemDirectory(at: item, result: { itemResult in guard let itemIsDirectory = itemResult else { print("无法判断项目类型") return } if itemIsDirectory { print("Item is a folder! Name:", item.lastPathComponent) contentsOfDirectory(url: item, contents: { contents in for content in contents { lookToURL(item: content) } }) } else { print("Item is a file! Name:", item.lastPathComponent) } }) } func contentsOfDirectory(url: URL, contents: ([URL]) -> Void) { do { let contentsOfDirectory = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: [.isDirectoryKey]) contents(contentsOfDirectory) } catch let error { print(error.localizedDescription) } } func isItemDirectory(at url: URL, result: (Bool?) -> Void) { do { let values = try url.resourceValues(forKeys: [.isDirectoryKey]) result(values.isDirectory) } catch let error { print(error.localizedDescription) result(nil) } } func isItemDirectory(at url: URL) -> Bool? { do { let values = try url.resourceValues(forKeys: [.isDirectoryKey]) return values.isDirectory } catch let error { print(error.localizedDescription) return nil } }
关键修改点
- 在
fileImporter成功回调中,对选中的文件夹URL调用startAccessingSecurityScopedResource()申请临时权限,并用defer确保权限自动释放 - 简化
contentsOfDirectory函数,移除冗余的文件存在性判断,直接通过异常处理捕获错误 - 优化可选值绑定逻辑,让代码更简洁易读
内容的提问来源于stack exchange,提问作者swiftPunk
相关产品推荐
相关产品推荐

