文件提供程序 iOS11 未调用 startProvidingItem
File Provider iOS11 startProvidingItem not invoked
我正在为 iOS 11.
实施文件提供程序扩展
尽管在 https://developer.apple.com/videos/play/wwdc2017/243/ 观看了会议并浏览了 Apple 的文档,但我似乎仍然无法理解如何实现 NSFileProviderExtension 和 NSFileProviderEnumerator objects.[=15= 的一些方法]
我成功实现了 NSFileProviderItem,它们都列在 Navite iOS 11 Files App 中。但是,我无法在选择文件时触发任何基于文档的应用程序打开。
我覆盖了 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用于测试。它 returns 具有所需属性的 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(at url:)。
现在已经解决了
干杯
-nls
编辑:
为了在您的文件提供程序中列出项目,应该实施 enumerator(for:) 方法。
这个方法会收到一个containerItemIdentifier,好像告诉你"what folder the user is trying to access"。它 returns 一个 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。文件和文件夹项目非常相似,并且在 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(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
我想我会提供后续答案,主要答案作为第一步非常好。在我的例子中,startProvidingItem 没有被调用,因为我没有将文件存储在系统正在寻找的目录中,也就是说:
<Your container path>/File Provider Storage/<itemIdentifier>/My Awesome Image.png
那是在 WWDC17 的幻灯片上的 FileProvider 扩展,但我不认为它必须完全遵循该格式。
我有一个未命名的目录 "File Provider Storage",我将文件直接放入其中,并且从未调用过 startProvidingItem。只有当我为放置文件的 uniqueFileID 创建了一个目录,并将我的整个存储目录重命名为 "File Provider Storage" 时,才调用了 startProvidingItem。
另请注意,对于 iOS11,您还需要提供对 FileProviderExtension 的 providePlaceholder 调用,完全使用文档中的代码除非您确定自己在做什么,否则不要偏离。
我正在为 iOS 11.
实施文件提供程序扩展尽管在 https://developer.apple.com/videos/play/wwdc2017/243/ 观看了会议并浏览了 Apple 的文档,但我似乎仍然无法理解如何实现 NSFileProviderExtension 和 NSFileProviderEnumerator objects.[=15= 的一些方法]
我成功实现了 NSFileProviderItem,它们都列在 Navite iOS 11 Files App 中。但是,我无法在选择文件时触发任何基于文档的应用程序打开。
我覆盖了 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用于测试。它 returns 具有所需属性的 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(at url:)。
现在已经解决了
干杯
-nls
编辑:
为了在您的文件提供程序中列出项目,应该实施 enumerator(for:) 方法。 这个方法会收到一个containerItemIdentifier,好像告诉你"what folder the user is trying to access"。它 returns 一个 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。文件和文件夹项目非常相似,并且在 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(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
我想我会提供后续答案,主要答案作为第一步非常好。在我的例子中,startProvidingItem 没有被调用,因为我没有将文件存储在系统正在寻找的目录中,也就是说:
<Your container path>/File Provider Storage/<itemIdentifier>/My Awesome Image.png
那是在 WWDC17 的幻灯片上的 FileProvider 扩展,但我不认为它必须完全遵循该格式。
我有一个未命名的目录 "File Provider Storage",我将文件直接放入其中,并且从未调用过 startProvidingItem。只有当我为放置文件的 uniqueFileID 创建了一个目录,并将我的整个存储目录重命名为 "File Provider Storage" 时,才调用了 startProvidingItem。
另请注意,对于 iOS11,您还需要提供对 FileProviderExtension 的 providePlaceholder 调用,完全使用文档中的代码除非您确定自己在做什么,否则不要偏离。