将文本文件保存到选定的目录,并在应用程序关闭后保留选定的目录
Saving text files to a selected directory, and persisting the chosen directory after the app has closed
我试图让用户选择将文件保存在 his/her 选择的目录中。当我弹出一个 NSOpenPanel()
让用户 select 目录时,我可以在那里保存一个文本文件。但是,如果我保存该路径并稍后尝试在其中保存更多文件而不再次打开 NSOpenPanel()
,则会出现错误。
是的,我知道应用程序的沙盒性质,因此我要求用户 select 一个文件夹。
如何将文件保存到用户选择的目录,并确保应用程序可以继续在该目录中保存文件。
这是我试过的代码。
打开面板:
func saveNewFile(filename: String) {
let contents = "Some text..."
@AppStorage("filesDirectory") var filesDirectory: String = ""
print("saving in: \(filesDirectory)")
do
{
let panel = NSOpenPanel()
panel.allowsMultipleSelection = false
panel.canChooseDirectories = true
panel.canChooseFiles = false
if panel.runModal() == .OK {
filesDirectory = panel.url?.path ?? "<none>"
}
let directoryURL: URL = panel.url!
let documentURL = directoryURL.appendingPathComponent (filename + ".txt")
print(documentURL)
try contents.write (to: documentURL, atomically: false, encoding: .utf8)
}
catch
{
print("An error occured: \(error)")
}
}
没有它
func saveNewFile(filename: String) {
let contents = "Some text..."
@AppStorage("filesDirectory") var filesDirectory: String = ""
print("saving in: \(filesDirectory)")
do
{
/*let panel = NSOpenPanel()
panel.allowsMultipleSelection = false
panel.canChooseDirectories = true
panel.canChooseFiles = false
if panel.runModal() == .OK {
filesDirectory = panel.url?.path ?? "<none>"
}*/
let directoryURL: URL = URL(string: filesDirectory)!
let documentURL = directoryURL.appendingPathComponent (filename + ".txt")
print(documentURL)
try contents.write (to: documentURL, atomically: false, encoding: .utf8)
}
catch
{
print("An error occured: \(error)")
}
}
我不熟悉 @AppStorage
的集成。这是 安全范围书签 和 UserDefaults.standard
的示例
首先创建一个自定义错误和一个方便的方法来可靠地访问安全范围。
enum ResolveError : Error { case cancelled }
extension URL {
func accessSecurityScopedResource<Value>(at url : URL, accessor: (URL) throws -> Value) rethrows -> Value {
let didStartAccessing = startAccessingSecurityScopedResource()
defer { if didStartAccessing { stopAccessingSecurityScopedResource() }}
return try accessor(url)
}
}
这个方法看起来很复杂,其实不然。 defer
表达式确保安全范围以受控方式保留,throws - rethrows
表达式允许 运行 闭包中的非抛出和抛出代码,您甚至可以 return 来自闭包的值,例如通过注释类型
let numberOfPages = baseURL.accessSecurityScopedResource(at: fileURL) { url -> Int in
guard let pdfDocument = PDFDocument(url: url) else { return 0 }
return pdfDocument.pageCount
}
然后创建一个方法来解析书签数据并在缺少数据的情况下显示打开的对话框
func resolveURL(for key: String) throws -> URL {
if let data = UserDefaults.standard.data(forKey: key) {
var isStale = false
let url = try URL(resolvingBookmarkData: data, options:[.withSecurityScope], bookmarkDataIsStale: &isStale)
if isStale {
let newData = try url.bookmarkData(options: [.withSecurityScope])
UserDefaults.standard.set(newData, forKey: key)
}
return url
} else {
let panel = NSOpenPanel()
panel.allowsMultipleSelection = false
panel.canChooseDirectories = true
panel.canChooseFiles = false
if panel.runModal() == .OK,
let url = panel.url {
let newData = try url.bookmarkData(options: [.withSecurityScope])
UserDefaults.standard.set(newData, forKey: key)
return url
} else {
throw ResolveError.cancelled
}
}
}
注意:isStale
参数的行为非常脆弱。您可以添加更精细的错误处理。
将一些内容保存到安全范围内的文件中写入
let filename = "test"
let contents = "Some Content"
do {
let directoryURL = try resolveURL(for: "testURL")
print(directoryURL)
let documentURL = directoryURL.appendingPathComponent (filename + ".txt")
try directoryURL.accessSecurityScopedResource(at: documentURL) { url in
try contents.write (to: url, atomically: false, encoding: .utf8)
}
} catch let error as ResolveError {
print("Resolve error:", error)
} catch {
print(error)
}
我试图让用户选择将文件保存在 his/her 选择的目录中。当我弹出一个 NSOpenPanel()
让用户 select 目录时,我可以在那里保存一个文本文件。但是,如果我保存该路径并稍后尝试在其中保存更多文件而不再次打开 NSOpenPanel()
,则会出现错误。
是的,我知道应用程序的沙盒性质,因此我要求用户 select 一个文件夹。
如何将文件保存到用户选择的目录,并确保应用程序可以继续在该目录中保存文件。
这是我试过的代码。
打开面板:
func saveNewFile(filename: String) {
let contents = "Some text..."
@AppStorage("filesDirectory") var filesDirectory: String = ""
print("saving in: \(filesDirectory)")
do
{
let panel = NSOpenPanel()
panel.allowsMultipleSelection = false
panel.canChooseDirectories = true
panel.canChooseFiles = false
if panel.runModal() == .OK {
filesDirectory = panel.url?.path ?? "<none>"
}
let directoryURL: URL = panel.url!
let documentURL = directoryURL.appendingPathComponent (filename + ".txt")
print(documentURL)
try contents.write (to: documentURL, atomically: false, encoding: .utf8)
}
catch
{
print("An error occured: \(error)")
}
}
没有它
func saveNewFile(filename: String) {
let contents = "Some text..."
@AppStorage("filesDirectory") var filesDirectory: String = ""
print("saving in: \(filesDirectory)")
do
{
/*let panel = NSOpenPanel()
panel.allowsMultipleSelection = false
panel.canChooseDirectories = true
panel.canChooseFiles = false
if panel.runModal() == .OK {
filesDirectory = panel.url?.path ?? "<none>"
}*/
let directoryURL: URL = URL(string: filesDirectory)!
let documentURL = directoryURL.appendingPathComponent (filename + ".txt")
print(documentURL)
try contents.write (to: documentURL, atomically: false, encoding: .utf8)
}
catch
{
print("An error occured: \(error)")
}
}
我不熟悉 @AppStorage
的集成。这是 安全范围书签 和 UserDefaults.standard
首先创建一个自定义错误和一个方便的方法来可靠地访问安全范围。
enum ResolveError : Error { case cancelled }
extension URL {
func accessSecurityScopedResource<Value>(at url : URL, accessor: (URL) throws -> Value) rethrows -> Value {
let didStartAccessing = startAccessingSecurityScopedResource()
defer { if didStartAccessing { stopAccessingSecurityScopedResource() }}
return try accessor(url)
}
}
这个方法看起来很复杂,其实不然。 defer
表达式确保安全范围以受控方式保留,throws - rethrows
表达式允许 运行 闭包中的非抛出和抛出代码,您甚至可以 return 来自闭包的值,例如通过注释类型
let numberOfPages = baseURL.accessSecurityScopedResource(at: fileURL) { url -> Int in
guard let pdfDocument = PDFDocument(url: url) else { return 0 }
return pdfDocument.pageCount
}
然后创建一个方法来解析书签数据并在缺少数据的情况下显示打开的对话框
func resolveURL(for key: String) throws -> URL {
if let data = UserDefaults.standard.data(forKey: key) {
var isStale = false
let url = try URL(resolvingBookmarkData: data, options:[.withSecurityScope], bookmarkDataIsStale: &isStale)
if isStale {
let newData = try url.bookmarkData(options: [.withSecurityScope])
UserDefaults.standard.set(newData, forKey: key)
}
return url
} else {
let panel = NSOpenPanel()
panel.allowsMultipleSelection = false
panel.canChooseDirectories = true
panel.canChooseFiles = false
if panel.runModal() == .OK,
let url = panel.url {
let newData = try url.bookmarkData(options: [.withSecurityScope])
UserDefaults.standard.set(newData, forKey: key)
return url
} else {
throw ResolveError.cancelled
}
}
}
注意:isStale
参数的行为非常脆弱。您可以添加更精细的错误处理。
将一些内容保存到安全范围内的文件中写入
let filename = "test"
let contents = "Some Content"
do {
let directoryURL = try resolveURL(for: "testURL")
print(directoryURL)
let documentURL = directoryURL.appendingPathComponent (filename + ".txt")
try directoryURL.accessSecurityScopedResource(at: documentURL) { url in
try contents.write (to: url, atomically: false, encoding: .utf8)
}
} catch let error as ResolveError {
print("Resolve error:", error)
} catch {
print(error)
}