SwiftUI:如何使用安全作用域保存URL书签
安全作用域书签实现方案
一、替换URL存储为书签数据存储
直接把URL存到@ApplicationStorage,App重启后会因为沙箱权限限制无法访问文件。必须将每个文件URL转换为安全作用域书签(Security-Scoped Bookmark),以Data类型存储。
修改你的存储变量:
// 替换原有的fileURL: [URL] @ApplicationStorage("fileBookmarks") private var fileBookmarks: [Data] = []
二、生成并存储安全书签
在fileImporter的成功回调里,替换原URL存储逻辑,改为生成书签并保存:
.fileImporter( isPresented: $openFile, allowedContentTypes: [.image, .pdf], allowsMultipleSelection: true, onCompletion: { result in switch result { case .success(let files): images.removeAll() var newBookmarks: [Data] = [] for file in files { let gotAccess = file.startAccessingSecurityScopedResource() defer { if gotAccess { file.stopAccessingSecurityScopedResource() } } guard gotAccess else { continue } // 保留原有图片读取逻辑 if let imageData = try? Data(contentsOf: file), let image = UIImage(data: imageData) { images.append(image) } // 生成安全书签 do { let bookmark = try file.bookmarkData( options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil ) newBookmarks.append(bookmark) } catch { print("生成书签失败: \(error)") } } // 存储书签数组 fileBookmarks = newBookmarks case .failure(let error): print("文件导入失败: \(error)") } } )
三、从书签恢复URL并获取访问权限
当需要再次访问文件时,从存储的书签数据恢复URL,并重新申请安全作用域权限:
1. 编写URL恢复工具函数
func restoreURL(from bookmarkData: Data) -> URL? { var isStale = false do { let url = try URL( resolvingBookmarkData: bookmarkData, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale ) // 书签过期时重新生成并更新存储 if isStale { guard url.startAccessingSecurityScopedResource() else { return nil } defer { url.stopAccessingSecurityScopedResource() } let newBookmark = try url.bookmarkData( options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil ) if let index = fileBookmarks.firstIndex(of: bookmarkData) { fileBookmarks[index] = newBookmark } } return url } catch { print("恢复URL失败: \(error)") return nil } }
2. 使用恢复后的URL访问文件
比如在需要重新加载文件内容时:
func loadFilesFromBookmarks() { images.removeAll() for bookmarkData in fileBookmarks { guard let url = restoreURL(from: bookmarkData) else { continue } let gotAccess = url.startAccessingSecurityScopedResource() defer { if gotAccess { url.stopAccessingSecurityScopedResource() } } guard gotAccess else { continue } // 加载图片逻辑 if let imageData = try? Data(contentsOf: url), let image = UIImage(data: imageData) { images.append(image) } // PDF文件可对应处理,比如生成PDF文档实例 } }
四、关键注意事项
- 权限释放:用
defer语句确保获取安全作用域权限后,无论代码执行结果如何,都能及时释放权限,避免资源泄漏。 - 书签过期处理:当书签标记为
isStale时,必须重新生成书签并更新存储,否则后续无法访问对应文件。 - 错误处理:实际开发中不要用
try?忽略所有错误,建议针对权限不足、文件被删除等具体错误做用户提示。 - 沙箱配置:在Xcode的Signing & Capabilities中开启App Sandbox,并勾选「User Selected File」权限,确保App有权限访问用户选择的文件。
内容的提问来源于stack exchange,提问作者Peter Plass
相关产品推荐
相关产品推荐

