I am writing a Mac app in SwiftUI and would like to display a live-updated list of documents and folders with the ability to edit the files.
First, users selects any folder with Open File Dialog and then I save the URL into UserDefaults and attempt to read list of files and folders when the app launches.
Assuming I have a valid URL then I do the following:
// Open the folder referenced by URL for monitoring only.
monitoredFolderFileDescriptor = open(url.path, O_EVTONLY)
// Define a dispatch source monitoring the folder for additions, deletions, and renamings.
folderMonitorSource = DispatchSource.makeFileSystemObjectSource(fileDescriptor: monitoredFolderFileDescriptor, eventMask: .write, queue: folderMonitorQueue)
The app crashes when I call DispatchSource.makeFileSystemObjectSource
with the EXC_BREAKPOINT
exception.
FileManager.default.isReadableFile(atPath: url.path)
returns false which tells me I don't have permissions to access this folder.
The URL path is /Users/username/Documents/Folder
I have added NSDocumentsFolderUsageDescription
into the info plist.
It's not clear how I can ask for permission programmatically. Theoretically, my URL can point to any folder on the file system that the user selects in the Open Dialog. It's unclear what is the best practice to request permission only when necessary. Should I parse the URL for the "Documents" or "Downloads" string? I also have watched this WWDC video.
Thanks for reading, here's an example what of what I am trying to show.
Like @vadian said, this needs Secure Scoped Bookmark. If you user picks a folder from NSOpenPanel
, permission dialog not necessary. This answer helped me a lot.
I create new NSOpenPanel
which gives me URL?
, I pass this URL to the saveAccess()
function below:
let bookmarksPath = "bookmarksPath"
var bookmarks: [URL: Data] = [:]
func saveAccess(url: URL) {
do {
let data = try url.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil)
bookmarks[url] = data
NSKeyedArchiver.archiveRootObject(bookmarks, toFile: bookmarksPath)
} catch {
print(error)
}
}
After you save bookmark, you can access when you app launches.
func getAccess() {
guard let bookmarks = NSKeyedUnarchiver.unarchiveObject(withFile: bookmarksPath) as? [URL: Data] else {
print("Nothing here")
return
}
guard let fileURL = bookmarks.first?.key else {
print("No bookmarks found")
return
}
guard let data = bookmarks.first?.value else {
print("No data found")
return
}
var isStale = false
do {
let newURL = try URL(resolvingBookmarkData: data, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale)
newURL.startAccessingSecurityScopedResource()
print("Start access")
} catch {
print(error)
return
}
}
The code is very rough, but I hope it can help someone. After acquiring the newURL
, you can print content of a folder.
let files = try FileManager.default.contentsOfDirectory(at: newURL, includingPropertiesForKeys: [.localizedNameKey, .creationDateKey], options: .skipsHiddenFiles)
And don't forget to call .stopAccessingSecurityScopedResource()
when you done.