【问题标题】:File Provider iOS11 startProvidingItem not invoked未调用文件提供程序 iOS11 startProvidingItem
【发布时间】:2018-03-22 22:51:26
【问题描述】:

我正在为 iOS 11 实现文件提供程序扩展。

尽管在https://developer.apple.com/videos/play/wwdc2017/243/ 观看了会议并浏览了 Apple 的文档,但我似乎仍然无法理解如何为 NSFileProviderExtension 和 NSFileProviderEnumerator 对象实现一些方法。

我成功实现了 NSFileProviderItem,将它们全部列在 Navite iOS 11 文件应用程序中。但是,我无法在选择文件时触发任何基于文档的应用程序打开。

我重写了 NSFileProviderExtension 的所有方法。有些仍然是空的,但我放置了一个断点来检查它们何时被调用。

NSFileProviderExtension 看起来像这样:

class FileProviderExtension: NSFileProviderExtension {
    var db : [FileProviderItem]  = [] //Used "as" a database
...

    override func item(for identifier: NSFileProviderItemIdentifier) throws -> NSFileProviderItem {
        for i in db {
            if i.itemIdentifier.rawValue == identifier.rawValue {
                return i
            }
        }
        throw NSError(domain: NSCocoaErrorDomain, code: NSNotFound, userInfo:[:])
    }

    override func urlForItem(withPersistentIdentifier identifier: NSFileProviderItemIdentifier) -> URL? {
        guard let item = try? item(for: identifier) else {
            return nil
        }

        // in this implementation, all paths are structured as <base storage directory>/<item identifier>/<item file name>
        let manager = NSFileProviderManager.default
        let perItemDirectory = manager.documentStorageURL.appendingPathComponent(identifier.rawValue, isDirectory: true)

        return perItemDirectory.appendingPathComponent(item.filename, isDirectory:false)
    }

    // MARK: - Enumeration
    func enumerator(for containerItemIdentifier: NSFileProviderItemIdentifier) throws -> NSFileProviderEnumerator {
        var maybeEnumerator: NSFileProviderEnumerator? = nil

        if (containerItemIdentifier == NSFileProviderItemIdentifier.rootContainer) {
            maybeEnumerator = FileProviderEnumerator(enumeratedItemIdentifier: containerItemIdentifier)
            self.db = CustomData.getData(pid: containerItemIdentifier)

        } else if (containerItemIdentifier == NSFileProviderItemIdentifier.workingSet) {
            // TODO: instantiate an enumerator for the working set
        } else {

        }
        guard let enumerator = maybeEnumerator else {
            throw NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError, userInfo:[:])
        }
        return enumerator
    }

我的 enumerateItems 看起来像这样:

class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {

    override func enumerateItems(for observer: NSFileProviderEnumerationObserver, startingAt page: NSFileProviderPage) {

        let itens = CustomData.getData(pid: enumeratedItemIdentifier)
        observer.didEnumerate(itens)
        observer.finishEnumerating(upTo: nil)

    }

静态函数 CustomData.getData 用于测试。它返回一个具有所需属性的 NSFileProviderItem 数组。如会议中所述,应将其替换为数据库。

class CustomData {


    static func getData(pid : NSFileProviderItemIdentifier) -> [FileProviderItem] {
        return [
            FileProviderItem(uid: "0", pid: pid, name: "garden", remoteUrl : "https://img2.10bestmedia.com/Images/Photos/338373/GettyImages-516844708_54_990x660.jpg"),
            FileProviderItem(uid: "1", pid: pid, name: "car", remoteUrl : "https://static.pexels.com/photos/170811/pexels-photo-170811.jpeg"),
            FileProviderItem(uid: "2", pid: pid, name: "cat", remoteUrl : "http://www.petmd.com/sites/default/files/what-does-it-mean-when-cat-wags-tail.jpg"),
            FileProviderItem(uid: "3", pid: pid, name: "computer", remoteUrl : "http://mrslamarche.com/wp-content/uploads/2016/08/dell-xps-laptop-620.jpg")
        ]
    }

}

问题是,当用户按下文档时,urlForItem 被成功调用,但返回项目 url 时没有任何反应。

我做错了什么? 我在互联网上找不到任何示例。

干杯

-nls

【问题讨论】:

    标签: ios swift ios11 xcode9 ios-app-extension


    【解决方案1】:

    事实证明,我没有正确实现 providePlaceholder(at url:)。

    现在已经解决了。

    干杯

    -nls

    编辑:

    为了列出文件提供程序中的项目,应该实现 enumerator(for:) 方法。 这个方法会收到一个containerItemIdentifier,就好像告诉你“用户试图访问哪个文件夹”。它返回一个 NSFileProviderEnumerator 对象,你也应该实现它。

    下面是一个简单的 enumerator(for:) 方法的示例:

    class FileProviderExtension: NSFileProviderExtension {
    
        override func enumerator(for containerItemIdentifier: NSFileProviderItemIdentifier) throws -> NSFileProviderEnumerator {
    
            var enumerator: NSFileProviderEnumerator? = nil
    
            if (containerItemIdentifier == NSFileProviderItemIdentifier.rootContainer) {
                enumerator = FileProviderEnumerator(enumeratedItemIdentifier: containerItemIdentifier)
            }
            else {
                enumerator = FileProviderEnumerator(enumeratedItemIdentifier: containerItemIdentifier)
            }
            if enumerator == nill {
                throw NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError, userInfo:[:])
            }
            return enumerator
        }
    
        (...)
    
    }
    

    同样,正如我所说,FileProviderEnumerator 应该由您实现。这里的重要方法是 enumerateItems(for observer:, startingAt page:)

    它应该是这样的:

    class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
    
        func enumerateItems(for observer: NSFileProviderEnumerationObserver, startingAt page: NSFileProviderPage) { 
    
            if (enumeratedItemIdentifier == NSFileProviderItemIdentifier.rootContainer) {
    
                //Creating an example of a folder item
                let folderItem = FileProviderFolder()
                folderItem.parentItemIdentifier = enumeratedItemIdentifier  //<-- Very important
                folderItem.typeIdentifier = "public.folder"
                folderItem.name = "ExampleFolder"
                folderItem.id = "ExampleFolderID"
    
                //Creating an example of a file item
                let fileItem = FileProviderFile()
                fileItem.parentItemIdentifier = enumeratedItemIdentifier    //<-- Very important
                fileItem.typeIdentifier = "public.plain-text"
                fileItem.name = "ExampleFile.txt"
                fileItem.id = "ExampleFileID"
    
    
                self.itemList.append(contentsOf: [folderItem, fileItem])
                observer.didEnumerate(self.itemList)
                observer.finishEnumerating(upTo: nil)
    
            }
            else {
                //1 > Find directory name using "enumeratedItemIdentifier" property
                //2 > Fetch data from the desired directory
                //3 > Create File or Folder Items 
                //4 > Send items back using didEnumerate and finishEnumerating
            }
        }
    
        (...)
    
    }
    

    请记住,我们正在创建这些 FileProviderEnumerators,并为它们提供 containerItemIdentifier。此属性用于确定用户尝试访问的文件夹。

    非常重要的说明:每个项目(文件或文件夹)都应定义其 parentItemIdentifier 属性。如果未设置此属性,则当用户尝试打开父文件夹时,项目将不会出现。 此外,顾名思义,typeIdentifier 将保存项目的统一类型标识符 (UTI)。

    最后,我们应该实现的最后一个对象是 NSFileProviderItem。 File 和 Folder 项目非常相似,它们的 typeIdentifier 属性应该不同。 这是一个非常简单的文件夹示例:

    class FileProviderFolder: NSObject, NSFileProviderItem {
    
        public var id: String?
        public var name: String?
    
        var parentItemIdentifier: NSFileProviderItemIdentifier
        var typeIdentifier: String
    
        init() {
    
        }
    
        var itemIdentifier: NSFileProviderItemIdentifier {
            return NSFileProviderItemIdentifier(self.id!)
        }
    
        var filename: String {
            return self.name!
        }
    }
    

    itemIdentifier 非常重要,因为如前所述,此属性将在尝试枚举其内容时提供文件夹项的目录名称(请参阅 enumerator(for:) 方法)。

    EDIT2

    如果用户选择了一个文件,则应该调用 startProvidingItem(at url:) 方法。 此方法应执行 3 个任务:

    1 - 查找所选项目 ID(通常使用提供的 url,但您也可以使用数据库)

    2 - 将文件下载到本地设备,使其在指定的 url 可用。 Alamofire 会这样做;

    3 - 调用completionHandler;

    下面是这个方法的一个简单例子:

    class FileProviderExtension: NSFileProviderExtension {
    
    
        override func urlForItem(withPersistentIdentifier identifier: NSFileProviderItemIdentifier) -> URL? {
            // resolve the given identifier to a file on disk
            guard let item = try? item(for: identifier) else {
                return nil
            }
            // in this implementation, all paths are structured as <base storage directory>/<item identifier>/<item file name>
            let perItemDirectory = NSFileProviderManager.default.documentStorageURL.appendingPathComponent(identifier.rawValue, isDirectory: true)
            let allDir = perItemDirectory.appendingPathComponent(item.filename, isDirectory:false)
            return allDir
        }
    
        override func persistentIdentifierForItem(at url: URL) -> NSFileProviderItemIdentifier? { 
            // exploit that the path structure has been defined as <base storage directory>/<item identifier>/<item file name>, at urlForItem
            let pathComponents = url.pathComponents
            assert(pathComponents.count > 2)
            return NSFileProviderItemIdentifier(pathComponents[pathComponents.count - 2])
        }
    
        override func startProvidingItem(at url: URL, completionHandler: @escaping (Error?) -> Void) {
    
            guard
                let itemID = persistentIdentifierForItem(at: url),
                let item = try? self.item(for: itemID) as! FileProviderFile else {
                    return
            }
    
            DownloadfileAsync(
                file: item,
                toLocalDirectory: url,
                success: { (response) in
    
                    // Do necessary processing on the FileProviderFile object
                    // Example: setting isOffline flag to True
    
                    completionHandler(nil)
                },
                fail: { (response) in
                    completionHandler(NSFileProviderError(.serverUnreachable))
                }
            )
    
        }
    
        (...)
    }
    

    请注意,为了从 URL 中获取 ID,我使用推荐的方法:它自身的 URL 包含项目 ID。

    此 URL 在 urlForItem 方法中定义。

    希望这会有所帮助。

    -nls

    【讨论】:

    • 你能分享你的代码吗.. B'Çoz 我正在尝试实现文件提供程序扩展,但我无法在 iOS 11 文件应用程序上显示项目
    • 因此,您的应用将显示在 iOS 11 文件中,但不会显示任何内容。这是你的问题吗?
    • 是的,iOS 11 文件应用程序中不显示任何内容,我已经设置了文件提供程序应用程序,并且我的应用程序不是文档库应用程序
    • ExampleFolder 是空的,因为您需要为其内容实现一个枚举器(请参阅 FileProviderEnumerator 上的 cmets 中的 4 个步骤)。我的服务器没有提供任何缩略图,所以我没有实现这个功能。
    • 感谢您的回复,现在我可以在 ExampleFolder 中显示内容,但我卡在这个方法覆盖 func startProvidingItem(在我需要如何实现?
    【解决方案2】:

    我想我会提供一个后续答案,作为第一步,主要答案是很好的。在我的情况下 startProvidingItem 没有被调用,因为我没有将文件存储在系统正在寻找的目录中,也就是说:

    <Your container path>/File Provider Storage/<itemIdentifier>/My Awesome Image.png
    

    这是 WWDC17 关于 FileProvider 扩展的幻灯片,但我不认为它必须如此严格地遵循该格式。

    我有一个未命名为“文件提供程序存储”的目录,我将文件直接放入其中,并且从未调用 startProvidingItem。只有当我为放置文件的 uniqueFileID 创建一个目录,并将我的整个存储目录重命名为“文件提供程序存储”时,才会调用 startProvidingItem。

    另请注意,对于 iOS11,您还需要向 FileProviderExtension 提供 providePlaceholder 调用,请完全使用文档中的代码,除非您确定,否则不要偏离你在做什么。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多