Some success in getting this working as expected, but it’s clunky, since I have to show a please allow access to your documents folder each time the app runs (then the files save in the database properly). If I don’t show it, loading bookmarks does not re-constitute the same permissions outside of the sandbox.
I am using a shared Bookmark singleton class (as suggested in macos - File access in a sandboxed Mac app with swift - Stack Overflow).
I included the info.plist key for NSDocumentsFolderUsageDescription. This does not seem to do anything. It’s meant to prompt the user for permission to access their Documents folder but I couldn’t work out how to trigger this.
In AppDelegate applicationDidFinishLaunching, I show the allowFolder() function (which opens an NSOpenPanel on their Documents directory). This is the minimal code in applicationDidFinishLaunching which works each application instantiation (see below for the complete code for Bookmark class):
Bookmark.shared.allowFolder()
But I originally put in the following code, as suggested in the Stack Overflow posting, but it does not work when I re-run the application beyond the first time, even though it is loading the prior bookmark(s).
if let url = Bookmark.shared.bookmarkURL(), !Bookmark.shared.fileExists(url) {
Bookmark.shared.allowFolder()
Bookmark.shared.saveBookmarks()
} else {
Bookmark.shared.loadBookmarks()
}
In AppDelegate applicationWillTerminate, I also put a Bookmarks.shared.saveBookmarks() call just to ensure they are saved each time.
If I now override the newDocument method in a subclass of NSDocumentController, I can show an save panel and create a new database which works (as long as allowFolder() was called). It does not help that the user selects the location in the NSSavePanel without calling allowFolder() initially. So, I’m stuck with presenting an ugly NSOpenPanel at the app start or it doesn’t work.
Here is the Bookmark class code:
class Bookmark {
static var shared = Bookmark() // singleton
private init() {}
var bookmarks = [URL: Data]()
func fileExists(_ url: URL) -> Bool {
var isDir = ObjCBool(false)
let exists = FileManager.default.fileExists(atPath: url.path, isDirectory: &isDir)
return exists
}
func bookmarkURL() -> URL? {
let url = URL.documentsDirectory().appendingPathComponent("Bookmarks.dict")
return url
}
func loadBookmarks() {
if let url = bookmarkURL(), fileExists(url) {
do {
let fileData = try Data(contentsOf: url)
if let fileBookmarks = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(fileData) as! [URL: Data]? {
bookmarks = fileBookmarks
for bookmark in bookmarks {
restoreBookmark(bookmark)
}
}
} catch {
print("Couldn't load bookmarks")
}
} else {
print("Bookmarks URL does not exist...")
allowFolder()
saveBookmarks()
}
}
func saveBookmarks() {
if let url = bookmarkURL() {
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: bookmarks, requiringSecureCoding: false)
try data.write(to: url)
} catch {
print("Couldn't save bookmarks")
}
}
}
func storeBookmark(url: URL) {
do {
let data = try url.bookmarkData(options: NSURL.BookmarkCreationOptions.withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil)
bookmarks[url] = data
} catch {
Swift.print("Error storing bookmarks")
}
}
func restoreBookmark(_ bookmark: (key: URL, value: Data)) {
let restoredUrl: URL?
var isStale = false
Swift.print("Restoring \(bookmark.key)")
do {
restoredUrl = try URL.init(resolvingBookmarkData: bookmark.value, options: NSURL.BookmarkResolutionOptions.withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale)
} catch {
Swift.print("Error restoring bookmarks")
restoredUrl = nil
}
if let url = restoredUrl {
if isStale {
Swift.print("URL is stale")
} else {
if !url.startAccessingSecurityScopedResource() {
Swift.print("Couldn't access: \(url.path)")
}
}
}
}
@discardableResult func allowFolder(directoryURL: URL = URL.documentsDirectory()) -> URL?
{
let openPanel = NSOpenPanel()
openPanel.title = "Please choose a folder for database files."
openPanel.nameFieldLabel = "Folder:"
openPanel.prompt = "Allow Access To Documents"
openPanel.directoryURL = directoryURL
openPanel.allowsMultipleSelection = false
openPanel.canChooseDirectories = true
openPanel.canCreateDirectories = true
openPanel.canChooseFiles = false
openPanel.begin { (response) -> Void in
if response == NSApplication.ModalResponse.cancel {
return
}
if response == NSApplication.ModalResponse.OK {
if let url = openPanel.url {
Bookmark.shared.storeBookmark(url: url)
}
}
}
return openPanel.url
}
}
Any suggestions as to how to:
- Get the Apple provided NSDocumentsFolderUsageDescription dialog to show, so I can save bookmarks.
- Make the bookmarks work persistently?