AppDelegate 或 SceneDelegate 从文件接收 URL,但底层文件不存在

AppDelegate or SceneDelegate receive URL from Files, but the underlying file does not exist

我正在开发一个基于 MapKit 的 iOS 路由应用程序,我在其中实现了对以 GPX 开放标准(本质上 XML)编写的文件的支持,以编纂所有用户关于路由的信息,可以导出或导入以用于备份目的。

当用户在应用程序中导入回 GPX 文件时,我已经让他可以通过文件应用程序将文件共享到应用程序来完成。这样做时,AppDelegate 或 SceneDelegate 会根据当前 iOS 版本拦截文件 URL:

在 AppDelegate (<= 12.4) 中:

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        // Handle the URL in the url variable
}

在 SceneDelegate (>= 13) 中:

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
       // Handle the URL in the URLContexts.first?.url
}

在我的 Info.plist 中,标志 LSSupportsOpeningDocumentsInPlaceUIFileSharingEnabled 都设置为 YES.有一个 UTImportedTypeDeclarations 列出了 .gpx 扩展名,以便“文件”应用程序自动选择我的应用程序以打开它。

这个方法在几个月前毫不费力地工作了 pathURLContexts.first?.url 变量已填充,您可以就地访问 URL 指向的文件,读取它并解析其路由数据,而无需复制它或进行任何特殊处理。然后 url 被发送到适当的 ViewController 供用户审查,一切都很好。

收到工单后快进到现在,GPX 导入出现问题。调试显示,当用户在 iOS 中向应用程序发送 GPX 文件时,AppDelegate 或 SceneDelegate 收到似乎正确的 URL,但 URL [=37] 指向的文件=]根据 FileManager,直接不存在 。使用此扩展程序:

extension FileManager {
    public func exists(at url: URL) -> Bool {
        let path = url.path
        return fileExists(atPath: path)
    }
}

在AppDelegate/SceneDelegate中:

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    FileManager.default.exists(at: url) // Result: false
}

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    if let url = URLContexts.first?.url {
        FileManager.default.exists(at: url) // Result: false
    }
}

文件的来源并不重要,因为从 iCloud 或本地文件夹发送文件不会改变结果。使用物理设备或模拟器具有相同的结果。为什么 URL 无效?这是一个新的系统错误吗?或者,这是否是“文件”应用程序在 iOS 中接收文件的正确方式?在新的 iOS 15 中是否弃用了这种方法?或者我只是忘记了一些重要的事情?

感谢您的耐心等待。

发现问题了。当您从另一个应用程序的共享 sheet 打开文件并且 LSSupportsOpeningDocumentsInPlace 为 TRUE 时,在您可以从 URL 对象访问它之前,您必须调用 url.startAccessingSecurityScopedResource() 所以你是授予访问文件的权限。完成后,您必须对同一 URL 对象进行 stopAccessingSecurityScopedResource() 调用。下面的示例使用 defer 关键字来确保始终调用关闭方法。

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    if url.startAccessingSecurityScopedResource() {
        defer  {
            url.stopAccessingSecurityScopedResource()
        }
        FileManager.default.exists(at: url) // Result: true
    }
}

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    if let url = URLContexts.first?.url, url.startAccessingSecurityScopedResource() {
        defer  {
            url.stopAccessingSecurityScopedResource()
        }
        FileManager.default.exists(at: url) // Result: true
    }
}

此答案来自