macOS sandbox folder authorization
If macOS wants to be released on the Apple market, it must comply with Apple's sandbox protocol. In this way, the application's storage defaults to the sandbox path, which isolates the user's file system. At this time, I need to access /User/xxx/Library/ If you use a folder like Developer/, direct access will be denied. In this case, you must authorize it. Once you agree, access will be justified.
So how to perform this operation? The first thing that comes to mind is to open the folder first, then the current permissions will be available, and then solve the problem of still having permissions to access in the future. At this time, bookmark is used. Dongdong is used for this purpose. Let’s implement this function step by step.
This example takes authorizing the /user/xxx/Library/Developer/ path as an example.
First get the absolute path to the file.
func getAbsolutePath(path: String) -> String? {
let pw = getpwuid(getuid())
guard let home = pw?.pointee.pw_dir else {
return nil
}
let homePath = FileManager.default.string(withFileSystemRepresentation: home, length: Int(strlen(home)))
return "\(homePath)/\(path)"
}
let path = getAbsolutePath("/Library/Developer")
// /User/xxx/Library/Developer/
Open the folder and get permissions
func openFinder() {
let fm = FileManager.default
let filepath = getAbsolutePath(path: "Library/Developer/")!
print("filepath is \(filepath)")
let url = URL(filePath: filepath)
let dialog = NSOpenPanel()
dialog.message = "文件夹授权以提供服务"
dialog.prompt = "选择"
dialog.allowsOtherFileTypes = false
dialog.canChooseFiles = false
dialog.canChooseDirectories = true
dialog.directoryURL = url
dialog.begin {
response in
if response == .OK, let folderPath = dialog.url {
// 可以判断一下是不是想要的文件夹,这个时候已经有权限了。
// 保存书签权限
}
}
}
Save bookmark permissions
Save the bookmark data in the bookmark and retrieve it for later use.
private func saveBookmarkData(for workDir: URL) {
do {
let bookmarkData = try workDir.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil)
print("save book mark data")
// save in UserDefaults
UserDefaults.standard.setValue(bookmarkData, forKey: "bookmark")
} catch {
print("Failed to save bookmark data for \(workDir)", error)
}
}
Determine whether the path is authorized
private func restoreFileAccess() -> Bool {
do {
if let bookmark = UserDefaults.standard.object(forKey: "bookmark") as? Data {
var isStale = false
let url = try URL(resolvingBookmarkData: bookmark, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale)
guard url.startAccessingSecurityScopedResource() else {
return false
}
return true
}
return false
} catch {
print("Error resolving bookmark:", error)
return false
}
}
Complete code
The following is the complete code
import SwiftUI
struct ContentView: View {
@State var fileSize: Int = 0
@State var canAccess: Bool = false
func getAbsolutePath(path: String) -> String? {
let pw = getpwuid(getuid())
guard let home = pw?.pointee.pw_dir else {
return nil
}
let homePath = FileManager.default.string(withFileSystemRepresentation: home, length: Int(strlen(home)))
return "\(homePath)/\(path)"
}
private func saveBookmarkData(for workDir: URL) {
do {
let bookmarkData = try workDir.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil)
print("save book mark data")
// save in UserDefaults
UserDefaults.standard.setValue(bookmarkData, forKey: "bookmark")
} catch {
print("Failed to save bookmark data for \(workDir)", error)
}
}
private func restoreFileAccess() -> Bool {
do {
if let bookmark = UserDefaults.standard.object(forKey: "bookmark") as? Data {
var isStale = false
let url = try URL(resolvingBookmarkData: bookmark, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale)
guard url.startAccessingSecurityScopedResource() else {
return false
}
return true
}
return false
} catch {
print("Error resolving bookmark:", error)
return false
}
}
func openFinder() {
let fm = FileManager.default
let filepath = getAbsolutePath(path: "Library/Developer/")!
print("filepath is \(filepath)")
let url = URL(filePath: filepath)
let dialog = NSOpenPanel()
dialog.message = "Choose your directory"
dialog.prompt = "Choose"
dialog.allowsOtherFileTypes = false
dialog.canChooseFiles = false
dialog.canChooseDirectories = true
dialog.directoryURL = url
dialog.begin {
response in
if response == .OK, let folderPath = dialog.url {
saveBookmarkData(for: folderPath)
}
}
}
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("占用容量: \(fileSize)")
Button {
if !canAccess {
openFinder()
}
} label: {
if canAccess {
Text("已授权")
} else {
Text("授权")
}
}
}
.padding()
.onAppear {
if restoreFileAccess() {
canAccess = true
} else {
canAccess = false
}
print("access is \(canAccess)")
}
}
}
#Preview {
ContentView()
}