ios - 保存 UIDocument 失败并出现权限错误 - `NSCocoaErrorDomain` 代码 `513`
问题描述
我正在尝试构建与 Pages / Numbers / Keynote 具有类似行为的 iOS 应用程序。这些应用程序中的每一个都是基于文档的应用程序,其中首先向用户呈现UIDocumentBrowserViewController
用户选择要在应用程序中打开的文档的位置。例如,在 Numbers 中,用户可以选择一个.numbers
文件并将其打开,或者用户可以选择一个.csv
并将此 csv 文件导入到一个 numbers 文件中,该文件与原始 csv 一起保存在同一位置。
在我的应用程序中,我希望用户选择一个.csv
文件,然后将其导入我自己的文档格式(.pivot
称为我在设备上的代码调用save(to:for:completionHandler:)
自定义 Pivot 文档时出现错误。
我的文档浏览器代码如下。
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)
}
})
}
}
}
}
我第一个UIDocument
加载 csv 文件的子类如下。
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")
}
不再有权限错误。希望这对将来的其他人有所帮助。
推荐阅读
- java - enum of Color Codes
- sql - 连接多个表,更好的方法 RDBMS
- python - Using powershell as Admin in python
- c# - Mono 在 Unity3d 中缺少 X509CertificateCollection ClientCertificates,有没有办法将它添加到库或资产中?
- python - python脚本转换后变量错误。他们属于皈依的那一刻
- c# - ListView 进入列表
- web - 来自网站的 UDP 连接
- c++ - QListView::doubleClicked 的插槽未被调用
- linq - Linq获取孩子的孩子的名字
- linux - 在bash中将一个数字等分