【问题标题】:Saving text files to a selected directory, and persisting the chosen directory after the app has closed将文本文件保存到选定的目录,并在应用关闭后保留选定的目录
【发布时间】:2021-08-20 16:41:10
【问题描述】:

我试图让用户选择将文件保存在他/她选择的目录中。当我弹出一个NSOpenPanel() 让用户选择目录时,我可以在那里保存一个文本文件。但是,如果我保存该路径并稍后尝试在其中保存更多文件而不再次打开NSOpenPanel(),则会出现错误。 是的,我知道应用程序的沙盒性质,因此我要求用户选择一个文件夹。

如何将文件保存到用户选择的目录,并确保应用程序可以继续在该目录中保存文件。

这是我尝试过的代码。

打开面板:

  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)")
    }
}

【问题讨论】:

  • 你得到了什么错误?
  • 错误:发生错误:错误域=NSCocoaErrorDomain Code=518“文件无法保存,因为不支持指定的 URL 类型。” UserInfo={NSURL=/Users/aleph/test/myfiles/newfile.txt}
  • 您必须创建一个 Security Scoped Bookmark,如 here 所述。无论如何URL(string 是错误的。你必须使用URL(fileURLWithPath
  • 不要混合使用 URL 和字符串路径,您正在保存一个文件路径,但在读取它时您将其视为 url。请改用URL(fileURLWithPath: filesDirectory)
  • @joakim 谢谢,现在返回““您无权将文件“newfile.txt”保存在“myfiles”文件夹中。”那么,我如何保存文件而不必每次都要求用户选择一个目录?

标签: swift macos macos-big-sur


【解决方案1】:

我不熟悉@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 表达式允许在闭包中运行非抛出和抛出代码,您甚至可以通过注释类型从闭包中返回一个值例如

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)
}

【讨论】:

  • 谢谢。这行得通,现在我需要花一点时间了解您所做的事情,以便我可以将其用于将来需要更新的文件写入。
猜你喜欢
  • 1970-01-01
  • 2015-10-17
  • 2016-12-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-28
  • 1970-01-01
相关资源
最近更新 更多