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

3
我正在开发一个基于MapKit的iOS路线规划应用程序,已经实现了对GPX开放标准(基本上是XML)文件的支持,以编码所有用户有关路线的信息,这些信息可以导出或导入进行备份。
当用户通过文件共享功能将GPX文件导入应用程序时,我已经做到了在Files应用程序中实现此功能。在这样做时,AppDelegate或SceneDelegate会拦截文件URL,具体取决于当前的iOS版本:
在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扩展名,以便Files应用程序自动选择我的应用程序来打开它。
几个月前,这种方法非常容易使用,pathURLContexts.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)
    }
}

在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中是否被弃用了?还是我忘记了一些关键的东西?

感谢您的耐心。


没有 FileManagerexists(at: URL) 方法。你能展示一下你的实现吗? - Leo Dabus
哎呀,忘记了它是一个扩展,已经添加到问题中了。 - Emanuele Tonetti
你的语法有误。应该是 openURLContexts.first?.url。顺便问一下,你能打印出它的选项吗?openURLContexts.first?.options - Leo Dabus
[__C.UIApplicationOpenURLOptionsKey(_rawValue: UIApplicationOpenURLOptionsSourceApplicationKey): com.apple.CoreSimulator.CoreSimulatorBridge, __C.UIApplicationOpenURLOptionsKey(_rawValue: UIApplicationOpenURLOptionsOpenInPlaceKey): 0]。 - Emanuele Tonetti
物理设备选项,iOS 15:可选(<UISceneOpenURLOptions: 0x282896180; sourceApp: (null); annotation: (null); openInPlace: YES; _eventAttribution: (null)>) - Emanuele Tonetti
显示剩余2条评论
1个回答

5

发现问题所在。当您从另一个应用程序的共享表中打开文件并且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
    }
}

这个答案摘自https://dev59.com/MVQJ5IYBdhLWcg3wKihi#62771024


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接