保存 UIDocument 失败,出现权限错误 - `NSCocoaErrorDomain` 代码 `513`

问题描述

我正在尝试构建与 Pages / Numbers / Keynote 具有类似行为的 iOS 应用程序。这些应用程序中的每一个都是基于文档的应用程序,用户首先会看到一个 uidocumentbrowserviewcontroller用户可以在其中选择要在应用程序中打开的文档。例如,在 Numbers 中,用户可以选择一个 .numbers 文件并将其打开,或者用户可以选择一个 .csv 并将此 csv 文件导入到一个与原始 csv 一起保存的数字文件中在同一位置。

在我的应用程序中,我希望用户选择一个 .csv 文件,然后我会将其导入我自己的文档格式(称为 .pivot)并将其与 csv 文件一起保存(就像数字。)这在模拟器中运行良好,但是当我在设备上运行我的代码时,在我的自定义 Pivot 文档上调用 save(to:for:completionHandler:) 时出现错误

我的文档浏览器代码如下。

class DocumentbrowserViewController: uidocumentbrowserviewcontroller,uidocumentbrowserviewcontrollerDelegate {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        delegate = self
        
        allowsDocumentCreation = false
        allowsPickingMultipleItems = false
    }
    
    func documentbrowser(_ controller: uidocumentbrowserviewcontroller,didPickDocumentsAt documentURLs: [URL]) {
        guard let sourceURL = documentURLs.first else { return }
        
        if sourceURL.pathExtension == "csv" {
            
            // Create a CSV document so we can read the CSV data
            let csvDocument = CSVDocument(fileURL: sourceURL)
            csvDocument.open { _ in
                
                guard let csv = csvDocument.csvData else {
                    fatalError("CSV is nil upon open")
                }
                
                // Create the file at the same location as the csv,with the same name just a different extension
                var pivotURL = sourceURL.deletingLastPathComponent()
                let pivotFilename = sourceURL.lastPathComponent .replacingOccurrences(of: "csv",with: "pivot")
                pivotURL.appendpathComponent(pivotFilename,isDirectory: false)
                
                let model = PivotModel()
                model.csv = csv
                let document = PivotDocument(fileURL: pivotURL)
                document.model = model
                
                document.save(to: pivotURL,for: .forCreating,completionHandler: { success in
                    
                    // `success` is false here
                    
                    dispatchQueue.main.async {
                        self.performSegue(withIdentifier: "presentPivot",sender: self)
                    }
                })
            }
        }
    }
    
}

我加载 csv 文件的第一个 UIDocument 子类如下。

import SwiftCSV // This is pulled in using SPM and works as I expect,so is unlikely causing this problem 

class CSVDocument: UIDocument {
    
    var csvData: CSV?
    
    override func contents(forType typeName: String) throws -> Any {
        return Data()
    }
    
    override func load(fromContents contents: Any,ofType typeName: String?) throws {
        guard let data = contents as? Data else {
            fatalError("No file data")
        }
        
        guard let string = String(data: data,encoding: .utf8) else {
            fatalError("Cannot load data into string")
        }
        
        csvData = try CSV(string: string)
    }
}

我的自定义 Pivot 文档的第二个 UIDocument 子类如下。通过覆盖 handleError() 函数,我可以看到保存失败并在 NSCocoaErrorDomain 中出现错误代码513

class PivotDocument: UIDocument {
    
    var model: PivotModel!
    var url: URL!
    
    override func contents(forType typeName: String) throws -> Any {
        let encoder = JSONEncoder()
        return try encoder.encode(model)
    }
    
    override func load(fromContents contents: Any,ofType typeName: String?) throws {        
        guard let data = contents as? Data else {
            fatalError("File contents are not Data")
        }
        
        let decoder = JSONDecoder()
        model = try decoder.decode(PivotModel.self,from: data)
    }
    
    override func handleError(_ error: Error,userInteractionPermitted: Bool) {
        let theError = error as NSError
        
        print("\(theError.code)") // 513
        print("\(theError.domain)") // NSCocoaErrorDomain
        print("\(theError.localizedDescription)") // “example.pivot” Couldn’t be moved because you don’t have permission to access “CSVs”.
        
        super.handleError(error,userInteractionPermitted: userInteractionPermitted)
    }
}

这在模拟器中有效(我的用户可以访问所有文件系统)但不适用于 iOS(用户和应用程序权限不同)这一事实让我觉得我有权限问题。例如,我需要在我的 Xcode 项目中声明一些权利吗?

或者我只是滥用了 UIDocument API,我是否需要找到不同的实现?

解决方法

我找到了我正在寻找的复制 iWork 应用程序功能的功能!

UIDocumentBrowserViewController 有这个函数 importDocument(at:nextToDocumentAt:mode:completionHandler:)。来自文档:

使用此方法将文档导入到与现有文档相同的文件提供程序和目录中。 例如,要复制已由文件提供者管理的文档: 在用户的临时目录中创建原始文件的副本。一定要给它一个唯一的名字。 调用importDocument(at:nextToDocumentAt:mode:completionHandler:),将临时文件的URL作为documentURL参数传入,将原始文件的URL作为neighborURL参数传入。

所以 documentBrowser(_:didPickDocumentsAt:) 现在是:

let pivotFilename = sourceURL.lastPathComponent .replacingOccurrences(of: "csv",with: "pivot")

let path = FileManager.default.temporaryDirectory.appendingPathComponent(pivotFilename)
if FileManager.default.createFile(atPath: path.path,contents: nil,attributes: nil) {
    
    self.importDocument(at: path,nextToDocumentAt: sourceURL,mode: .copy) { (importedURL,errorOrNil) in
        guard let pivotURL = importedURL else {
            fatalError("No URL for imported document. Error: \n \(errorOrNil?.localizedDescription ?? "NO ERROR")")
        }
    
        
        let model = PivotModel()
        model.csv = csv
        let document = PivotDocument(fileURL: pivotURL)
        document.model = model
        
        DispatchQueue.main.async {
            self.performSegue(withIdentifier: "presentPivot",sender: self)
        }
    }
}
else {
    fatalError("Could not create local pivot file in temp dir")
}

不再有权限错误。希望这能在未来对其他人有所帮助。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...