捕获从磁盘读取文件时出错 - 您没有权限

问题描述

我在 AppDel 中使用以下代码。当用户点击 gpx 文件或使用共享选项与我的应用程序共享文件时触发。在这一点上,是用户指定他们允许我的应用程序访问该文件,所以我对为什么这仍然被拒绝感到有些困惑。非常感谢任何建议。

func application(_ app: UIApplication,open url: URL,options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool {
    
    if let dir = FileManager.default.urls(for: .documentDirectory,in: .userDomainMask).first {
        let fileURL = dir.appendingPathComponent(url.lastPathComponent)
        if FileManager.default.fileExists(atPath: fileURL.path) {
            print("File already exists")
        }
        else {
            do {
                try FileManager.default.copyItem(at: url,to: fileURL)
                print("Did write file to disk")
            }
             catch {
                 dispatchQueue.main.async(){
                         print("Catch error writing file to disk: \(error)")
                 }
             }
        }
    }
    return true
}

错误打印到控制台如下:

Error Domain=NSCocoaErrorDomain Code=257 "文件 “Beech_Hill_Long_Route.gpx”无法打开,因为您没有 允许查看它。” UserInfo={NSFilePath=/private/var/mobile/Library/Mobile Documents/com~apple~CloudDocs/Desktop/Beech_Hill_Long_Route.gpx,NSUnderlyingError=0x282c19a70 {错误域=NSPOSIXErrorDomain 代码=1 “不允许操作”}}

解决方法

我不太了解它在 iOS 上的确切工作原理,但我对它在 macOS 上的工作原理有一些了解,两者应该相似。对于 macOS 沙盒应用,用户必须专门通过 NSOpenPanelNSSavePanel 选择沙盒外的文件。 iOS 上的等效项是 UIDocumentPickerViewController,但我认为显式共享也可以。在此基础上,如果我遇到您的问题,我会如何考虑它以及我会尝试什么:

您有两个 URL 参与了您对 FileManager.default.copy() 的调用。不清楚是哪个产生了错误,所以我会考虑两者。

首先让我们看看fileURL。您正在构建它以将文件放入用户的 Documents 目录中。至少在 macOS 上,该目录在应用程序的沙箱中,这意味着通过 NSSavePanel 询问用户(这也意味着他们可能决定将其放在其他地方)。您可能必须执行 UIKit 等效操作,或者只是确保您选择的位置位于您的沙箱中。

要测试它,而不是进行复制,请尝试写入 fileURL 以仅隔离那个。例如:

do { try "TestString".data(using: .utf8)?.write(url: fileURL) }
catch { print("Write failed: \(error.localizedDescription)") }

如果失败,那么您的问题在于 fileURL,在这种情况下,您可能需要使用 UIDocumentPickerViewController 来保存它,或者选择一个绝对位于应用沙箱中的位置。

如果测试成功,问题一定出在传入的 URL

我将假设 url 已经在安全范围内,因为我不确定否则共享将如何工作。我认为最有可能在幕后发生的是,当用户与您的应用共享 URL 时,iOS 从 URL 创建一个安全范围的书签并发送书签而不是 {{1} }} 到您的应用程序。然后在您的应用程序结束时,该书签用于在将 URL 传递给您的应用程序的委托之前重构它。如果我是对的,您需要打开和关闭安全范围才能使用它:

URL

请注意,url.startAccessingSecurityScopedResource() // access your file here url.stopAccessingSecurityScopedResource() 必须在主线程上调用,因此如果您的代码是异步发生的,则需要安排它在那里运行:

stopAccessingSecurityScopeResource()

如果您需要保存 url.startAccessingSecurityScopedResource() // access your file here DispatchQueue.main.async { url.stopAccessingSecurityScopedResource() } 本身以供将来运行您的程序时使用...您不能。嗯,你可以,但它不会是有效的,所以你会马上回到权限错误。相反,您必须保存一个书签,然后在以后的运行中,从书签中重建 URL

URL

let bookmark = try url.bookmarkData( options: .withSecurityScope,includingResourceValuesForKeys: nil,relativeTo: nil ) bookmark 的一个实例,因此您可以将其写入 Data 或您想要保存的任何位置。稍后从它获取 UserDefaults

URL

注意,如果初始化返回后var isStale = false let url = try URL( resolvingBookmarkData: bookmark,options: .withSecurityScope,relativeTo: nil,bookmarkDataIsStale: &isStale ) if isStale { let newBookmark = try url.bookmarkData( options: .withSecurityScope,relativeTo: nil ) // Save the new bookmark } isStale,则需要重新制作并重新保存书签。

很遗憾我们必须经历这么多麻烦,但我们生活在一个有些人坚持对其他人的设备和数据做坏事的世界中,因此我们必须处理这些问题以保护用户免受恶意数据泄露。