文件提供程序iOS11 startProvidingItem未被调用

问题描述 投票:6回答:2

我正在为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
    }

我的枚举项看起来像这样:

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
2个回答
8
投票

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

它现在已经解决了。

干杯

-nls

编辑:

为了列出文件提供程序中的项目,应该实现方法枚举器(for :)。此方法将接收containerItemIdentifier,就像告诉您“用户尝试访问的文件夹”一样。它返回一个NSFileProviderEnumerator对象,该对象也应由您实现。

下面是一个简单的枚举器(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(对于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
        }
    }

    (...)

}

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

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

最后,我们应该实现的最后一个对象是NSFileProviderItem。文件和文件夹项都非常相似,并且它们的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非常重要,因为如前所述,此属性将在尝试枚举其内容时提供文件夹项的目录名(请参阅枚举器(for :)方法)。

Aaditi

如果用户选择文件,则应调用方法startProvidingItem(在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在url For Item方法中定义。

希望这可以帮助。

-nls


2
投票

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

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

这是来自WWDC17的FileProvider扩展上的幻灯片,但我认为它不一定必须遵循这种格式。

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

另请注意,对于iOS11,您还需要向FileProviderExtension提供一个providePlaceholder调用,请使用文档中的代码,并且不要偏离,除非您确定自己在做什么。

© www.soinside.com 2019 - 2024. All rights reserved.