在 macOS 应用中使用 Bookmark 持久化对文件的访问权限
macOS 应用程序默认会开启 App Sandbox 应用沙盒,对于沙盒外的文件,需要用户主动授权才能进行访问。但是应用程序并不会主动保留对文件玩的访问权限,在应用重启后依旧需要再次授权才能访问。本文的主要内容是介绍如何通过 Bookmark 持久化对沙盒外的文件的访问权限。
2024-01-25
BookmarkData 是什么?
是 bookmarkData(options:includingResourceValuesForKeys:relativeTo:)
URL
URL
UserDefaults
URL
URL
使用 BookmarkData 持久化文件的访问权限
使用前需要确保应用程序启用了对应用沙盒外文件的访问权限,开启步骤参考下面的截图:
开启后根据下面的步骤持久化对文件夹的访问权限:
- 通过 读取目标文件夹并获取访问权限;
fileImporter
- 通过 方法创建书签(启用
URL.bookmarkData(options:includingResourceValuesForKeys:relativeTo:)
选项);withSecurityScope
- 使用 存储该书签数据;
UserDefaults
- 应用程序重启后,从 中读取
UserDefaults
;BookmarkData
- 使用 方法将
URL.init(resolvingBookmarkData:options:relativeTo:bookmarkDataIsStale:)
解析为BookmarkData
(启用URL
选项);withSecurityScope
- 如果 返回的布尔值为 true,则需要使用
bookmarkDataIsStale
重新创建书签,并更新应用程序中存储的书签版本;bookmarkData(options:includingResourceValuesForKeys:relativeTo:)
- 在解析后的 上调用
URL
开始访问;startAccessingSecurityScopedResource()
- 使用 访问目标文件;
URL
- 在解析后的 上调用
URL
停止访问。stopAccessingSecurityScopedResource()
下面的是一个使用
BookmarkData
import SwiftUI
struct BookmarkDataView: View {
@State var target: URL?
@State var selecting: Bool = false
@State var files: [URL] = []
var body: some View {
VStack {
HStack {
Text("目标路径" + (target?.path ?? "N/A"))
.padding()
Button("选择文件夹") {
selecting.toggle()
}.padding()
}
List(files, id: \.self) {file in
Text(file.path)
}.padding()
}
.onAppear {
if let url = loadBookmarkURL() {
scanTarget(at: url)
target = url
}
}
.fileImporter(isPresented: $selecting, allowedContentTypes: [.folder], allowsMultipleSelection: false, onCompletion: {
do {
let result = try $0.get()
if result.first != nil {
target = result.first!
saveBookmark(target: result.first!)
scanTarget(at: result.first!)
}
} catch {
print("\(error)")
}
})
}
// 保存 bookmarkData 到 UserDefaults
func saveBookmark(target: URL) {
do {
let bookmarkData = try target.bookmarkData(
options: [.withSecurityScope]
)
UserDefaults.standard.set(bookmarkData, forKey: "bookmark")
} catch {
print("书签数据保存失败:\(error)")
}
}
// 从 UserDefaults 中加载保存的 bookmarkData 并还原为文件路径
func loadBookmarkURL() -> URL? {
if let bookmarkData = UserDefaults.standard.data(forKey: "bookmark") {
do {
var isStale = false
let url = try URL(
resolvingBookmarkData: bookmarkData,
options: [.withoutUI, .withSecurityScope],
relativeTo: nil,
bookmarkDataIsStale: &isStale
)
return url
} catch {
print("书签数据解析失败 \(error)")
return nil
}
}
return nil
}
func scanTarget(at url: URL) {
let success = url.startAccessingSecurityScopedResource()
if !success {
print("加载失败")
return
}
do {
let fileManager = FileManager.default
files = try fileManager.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: [])
} catch {
print("Error: \(error)")
}
url.stopAccessingSecurityScopedResource()
}
}
#Preview {
BookmarkDataView()
}
BookmarkData.swift
在上面的代码中,点击“选择文件夹”按钮后会出现
fileImporter
files
此时关闭应用并重启后应用会直接读取
UserDefaults
BookmarkData
完整的代码可以在 Github 上查看。