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

Ema*_*tti 3 import file ios appdelegate swift

我正在开发一个基于 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
}
Run Code Online (Sandbox Code Playgroud)

在 SceneDelegate (>= 13) 中:

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

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

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

快进到现在收到票后,GPX 导入已损坏。调试显示,当在 iOS 中用户向应用程序发送 GPX 文件时,AppDelegate 或 SceneDelegate 收到的 URL 似乎是正确的,但根据 FileManager,该 URL直接指向的文件并不存在。使用此扩展:

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

在 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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

感谢您的耐心等待。

Ema*_*tti 5

发现出了什么问题。当您从另一个应用程序的共享表打开文件并且 为LSSupportsOpeningDocumentsInPlaceTRUE 时,您必须调用才能从 URL 对象访问该文件url.startAccessingSecurityScopedResource(),以便您有权访问该文件。完成后,您必须随后调用stopAccessingSecurityScopedResource()同一 URL 对象。下面的示例使用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
    }
}
Run Code Online (Sandbox Code Playgroud)

这个答案取自/sf/answers/4393971711/