我正在为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/ - /
- 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
事实证明,我没有正确实现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是非常重要的,因为,如前所述,此属性将文件夹项目试图枚举其内容(参见时提供目录名枚举(对于:)方法).
EDIT2
如果用户选择文件,则应调用方法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/ - /
- 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
/ - /
- , 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,我使用的是推荐的方法:它自己包含项目ID的URL.
此URL在urlForItem方法中定义.
希望这可以帮助.
-nls