首页 > 解决方案 > 将文本文件保存到选定的目录,并在应用关闭后保留选定的目录

问题描述

我试图让用户选择将文件保存在他/她选择的目录中。当我弹出一个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)")
    }
}

标签: swiftmacosmacos-big-sur

解决方案


我不熟悉@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)
}

推荐阅读