保存 UIDocument 失败并出现权限错误 - `NSCocoaErrorDomain` 代码 `513`
Saving UIDocument fails with permissions error - `NSCocoaErrorDomain` code `513`
我正在尝试构建 iOS 具有与 Pages / Numbers / Keynote 类似行为的应用程序。这些应用程序中的每一个都是基于文档的应用程序,用户首先会看到 UIDocumentBrowserViewController
,用户可以在其中选择要在应用程序中打开的文档。例如,在 Numbers 中,用户可以 select 一个 .numbers
文件并且它会打开,或者用户可以 select 一个 .csv
并且它会将这个 csv 文件导入一个数字文件它与原始 csv 一起保存在同一位置。
在我的应用程序中,我希望用户 select 一个 .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)
}
})
}
}
}
}
我的第一个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)
}
}
我的自定义数据透视文档的第二个 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:)
。来自文档:
Use this method to import a document into the same file provider and directory as an existing document.
For example, to duplicate a document that's already managed by a file provider:
Create a duplicate of the original file in the user's temporary directory. Be sure to give it a unique name.
Call importDocument(at:nextToDocumentAt:mode:completionHandler:), passing in the temporary file's URL as the documentURL parameter and the original file's URL as the neighborURL parameter.
所以 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")
}
没有更多的权限错误。希望这对以后的其他人有所帮助。
我正在尝试构建 iOS 具有与 Pages / Numbers / Keynote 类似行为的应用程序。这些应用程序中的每一个都是基于文档的应用程序,用户首先会看到 UIDocumentBrowserViewController
,用户可以在其中选择要在应用程序中打开的文档。例如,在 Numbers 中,用户可以 select 一个 .numbers
文件并且它会打开,或者用户可以 select 一个 .csv
并且它会将这个 csv 文件导入一个数字文件它与原始 csv 一起保存在同一位置。
在我的应用程序中,我希望用户 select 一个 .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)
}
})
}
}
}
}
我的第一个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)
}
}
我的自定义数据透视文档的第二个 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:)
。来自文档:
Use this method to import a document into the same file provider and directory as an existing document. For example, to duplicate a document that's already managed by a file provider: Create a duplicate of the original file in the user's temporary directory. Be sure to give it a unique name. Call importDocument(at:nextToDocumentAt:mode:completionHandler:), passing in the temporary file's URL as the documentURL parameter and the original file's URL as the neighborURL parameter.
所以 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")
}
没有更多的权限错误。希望这对以后的其他人有所帮助。