2017-10-11 139 views
4

我执行文件提供扩展的iOS 11.文件提供iOS11 startProvidingItem不调用

Dispite在https://developer.apple.com/videos/play/wwdc2017/243/收看了会议并通过苹果的文档浏览,我似乎仍不能了解如何实现一些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

回答

3

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

现在已经解决了。

干杯

-nls

编辑:

为了列出您的文件提供的项目,方法枚举(对于:)应予执行。 此方法将收到一个containerItemIdentifier,就好像告诉你“用户试图访问什么文件夹”一样。它返回一个NSFileProviderEnumerator对象,这也应该由你来实现。

下面是一个简单的枚举(对于:)方法应该如何看起来像一个例子:

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(观察员:, startingAt页:)

这是它应该如何看:

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。 “文件”和“文件夹”项目都非常相似,并且它们的类型标识符属性应该不同。 这里是一个文件夹中的一个很简单的例子:

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是非常重要的,因为,如前所述,此属性将文件夹项目试图枚举其内容(参见时提供目录名到枚举器(用于:)方法)。

EDIT2

如果用户选择了一个文件,所述方法startProvidingItem(在URL :)应该被调用。 此方法应执行3项任务:

1 - 查找所选项目ID(usualy使用所提供的网址,但您可以使用一个数据库太)

2 - 将文件下载到本地设备,使它可在指定的网址找到。阿拉莫菲尔这样做;

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,我使用的方法recomended:网址是自包含的项目ID。

该URL在urlForItem方法中定义。

希望这会有所帮助。

-nls

+0

请问您可以分享您的代码吗?B'Çoz我正在尝试实施文件提供商扩展程序,但我无法在iOS 11上显示项目文件应用程序 –

+0

因此,您的应用程序将显示在iOS 11文件中,不显示任何内容。这是你的问题吗? – nfls

+0

是不显示iOS 11文件应用程序中的任何内容,我已设置文件提供程序应用程序,并且我的应用程序不是基于文档的应用程序 –

1

我想我会提供后续的答案,主要答案是伟大的第一步。在我的情况startProvidingItem不叫,因为我没有存储在完全相同的系统寻找目录中的文件,即:

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

也就是说从WWDC17上FileProvider扩展幻灯片,但我并不认为它必须遵循这种格式。

我有一个目录没有命名为“文件提供程序存储”,我直接将文件放入其中,并且startProvidingItem从未被调用过。只有当我为放置该文件的uniqueFileID制作一个目录时,AND将我的整体存储目录重命名为startProvidingItem被调用的“文件提供程序存储”。

也要注意,使用iOS11,你需要提供providePlaceholder呼叫以及到FileProviderExtension,用了整整那就是在对文档的代码,不偏离,除非你确信你是什么这样做。