macOS 文件访问权限持久化

969 阅读1分钟

需要解决的问题

macOS 通过 NSOpenPanel 选择需要访问的文件或者文件夹后,当退出项目后,再次启动时,若再想访问之前的选择的文件或者文件夹,如果没有对文件或者文件夹的 URL 做额外的处理,App 是没有访问权限的。

解决方法

将 URL 进行 Security-Scoped Bookmarks 处理保存在沙盒。

一、项目需要的设置

选择项目,TARGETS -> Signing & Capabilities -> App Sandbox,对选择的文件进行权限选择。 User Selected File 选择 Read Only 或者 Read/Write。

截屏2023-08-02 14.10.10.png

二、这里以打开 PDF 文件为例,保存 URL

// PDF 文件的 URL
var fileURL: URL?
    
// MARK: 选择文件
    @IBAction func selectFileEvent(_ sender: Any) {

        let openFilePanel = NSOpenPanel()
        openFilePanel.allowedFileTypes = ["pdf"]
        openFilePanel.allowsMultipleSelection = false
        openFilePanel.canChooseFiles = true
        openFilePanel.canCreateDirectories = false
        openFilePanel.canChooseDirectories = false
        openFilePanel.begin { (response) in
            if response == .OK {
                if let url = openFilePanel.urls.first {

                    self.storeBookMark(url: url)

                    self.fileURL = url
                }
            }
        }
    }

保存 URL,通过 bookmark​Data(options:​including​Resource​Values​For​Keys:​relative​To:) 方法,将获取的 Data 持久化保存

// MARK: 保存 url bookmark
    private func storeBookMark(url: URL) {

        do {
            let urlData = try url.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil)
            
            // 可以保存到数据库,这里通过 UserDefaults 保存
            UserDefaults.standard.set(urlData, forKey: "save_file_url")
        } catch {
            print(error)
        }
    }

三、恢复 URL 的访问权限

当 App 再次启动的时候,在访问之前的文件或者文件夹时,需要从保存的位置读取 URL

// MARK: 加载保存的文件 URL
    private func loadBookMark() {

        let urlData = UserDefaults.standard.object(forKey: "save_file_url") as! Data
        var isStale = false
        do {

            let restoredURL = try URL(resolvingBookmarkData: urlData, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale)

            if isStale {
                // 如果数据已过期,重新请求权限
                _ = restoredURL.startAccessingSecurityScopedResource()
            }
            fileURL = restoredURL
        } catch {
        }
    }
    
 // MARK: 显示 PDF 文件
    private func setupPDFView() {
        if let url = fileURL {
        
            // 请求权限
            _ = url.startAccessingSecurityScopedResource()

            // 这个方法可以检查是否有访问权限
            if FileManager.default.isReadableFile(atPath: url.path) {
                print("can access")
            }

            let pdfDocument = PDFDocument(url: url)
            pdfView.document = pdfDocument
            pdfView.autoScales = true
            pdfView.scaleFactor = 1.0
            view.addSubview(pdfView, positioned: .below, relativeTo: topView)
            pdfView.snp.makeConstraints { make in
                make.top.equalTo(topView.snp.bottom)
                make.left.right.bottom.equalToSuperview()
            }
            
            url.stopAccessingSecurityScopedResource()
        }
    }

demo 下载