I'm implementing a File Provider Extension for iOS 11.
我正在为iOS 11实现文件提供程序扩展。
Dispite watching the conference at https://developer.apple.com/videos/play/wwdc2017/243/ and navigating through Apple's Documentation, I still can't seem to understand how to implement some of the methods for NSFileProviderExtension and NSFileProviderEnumerator objects.
在https://developer.apple.com/videos/play/wwdc2017/243/上观看会议并浏览Apple的文档时,我仍然无法理解如何实现NSFileProviderExtension和NSFileProviderEnumerator对象的某些方法。
I successfully implemented NSFileProviderItem, having all of them listed in the Navite iOS 11 Files App. However, I can't trigger any document based app to open upon selecting a file.
我成功实现了NSFileProviderItem,其中所有这些都列在了Navite iOS 11文件应用程序中。但是,我无法触发任何基于文档的应用程序在选择文件时打开。
I overrided all the methods for the NSFileProviderExtension. Some are still empty, but I placed a breakpoint to check whenever they are called.
我重写了NSFileProviderExtension的所有方法。有些仍然是空的,但是我设置了断点来检查它们何时被调用。
The NSFileProviderExtension looks something like this:
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
}
My enumerateItems looks something like so:
我的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)
}
The static function CustomData.getData is used for testing. It returns an array of NSFileProviderItem with the desired properties. It should be replaced with a database, as explained in the conference.
静态函数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")
]
}
}
The problem is, when the user presses a document, urlForItem is successfully called but nothing happens upon returning the item url.
问题是,当用户按下文档时,urlForItem被成功调用,但在返回项目URL时没有任何反应。
What am I doing wrong? I can't find any examples on the internet.
我究竟做错了什么?我在互联网上找不到任何例子。
Cheers
-nls
6
Turns out, I did not correctly implement providePlaceholder(at url:).
事实证明,我没有正确实现providePlaceholder(在url :)。
It is now solved.
它现在已经解决了。
Cheers
-nls
EDIT:
In order to list the items in your file provider, the method enumerator(for:) should be implemented. This method will receive a containerItemIdentifier, as if telling you "what folder the user is trying to access". It returns a NSFileProviderEnumerator object, that should also be implemented by you.
为了列出文件提供程序中的项目,应该实现方法枚举器(for :)。此方法将接收containerItemIdentifier,就像告诉您“用户尝试访问的文件夹”一样。它返回一个NSFileProviderEnumerator对象,该对象也应由您实现。
Here is an example of how a simple enumerator(for:) method should look like:
下面是一个简单的枚举器(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
}
(...)
}
Again, as I said, the FileProviderEnumerator should be implemented by you. The important method here is the enumerateItems(for observer:, startingAt page:)
同样,正如我所说,FileProviderEnumerator应该由您实现。这里重要的方法是enumerateItems(对于observer:,startingAt page :)
Here it is how it should look:
这是它应该如何看待:
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
}
}
(...)
}
Remember that we were creating these FileProviderEnumerators, giving them the containerItemIdentifier. This property is used to determine what folder the user is trying to access.
请记住,我们正在创建这些FileProviderEnumerator,为它们提供containerItemIdentifier。此属性用于确定用户尝试访问的文件夹。
Very important note: Each item, File or Folder, should have its parentItemIdentifier property defined. If this property is not set, the items won't appear when the user tries to open the parent folder. Also, as the name suggests, typeIdentifier will hold the Uniform Type Identifier (UTI) for the item.
非常重要的注意事项:每个项目(文件或文件夹)都应定义其parentItemIdentifier属性。如果未设置此属性,则当用户尝试打开父文件夹时,不会显示这些项。此外,顾名思义,typeIdentifier将保存项目的统一类型标识符(UTI)。
Finally, the last object we should implement is the NSFileProviderItem. Both File and Folder items are very similar, and should differ in their typeIdentifier property. Here is a very simple example of a folder:
最后,我们应该实现的最后一个对象是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!
}
}
The itemIdentifier is very important because, as stated before, this property will provide the directory name for the folder item when trying to enumerate its contents (refer to enumerator(for:) method).
itemIdentifier非常重要,因为如前所述,此属性将在尝试枚举其内容时提供文件夹项的目录名(请参阅枚举器(for :)方法)。
EDIT2
If the user selects a file, the method startProvidingItem(at url:) should be called. This method should perform 3 tasks:
如果用户选择文件,则应调用方法startProvidingItem(在url :)。此方法应执行3个任务:
1 - Find the selected item ID (usualy using the provided url, but you can use a database too)
1 - 查找所选的项目ID(通常使用提供的URL,但您也可以使用数据库)
2 - Download the file to the local device, making it available at the specified url. Alamofire does this;
2 - 将文件下载到本地设备,使其在指定的URL上可用。 Alamofire这样做;
3 - Call completionHandler;
3 - 调用completionHandler;
Here is a simple example of this method:
以下是此方法的一个简单示例:
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))
}
)
}
(...)
}
Note that, to get the ID from the URL, I'm using the recomended method: the URL it self contains the item ID.
请注意,要从URL获取ID,我使用推荐的方法:它自己包含项目ID的URL。
This URL is definedin the urlForItem method.
此URL在urlForItem方法中定义。
Hope this helps.
希望这可以帮助。
-nls
1
I thought I'd provide a followup answer, the primary answer is great as a first step. In my case startProvidingItem was not called because I was not storing the files in exactly the directory the system was looking for, that is to say:
我以为我会提供一个后续答案,作为第一步,主要答案是很好的。在我的情况下,没有调用startProvidingItem,因为我没有将文件存储在系统正在寻找的目录中,也就是说:
/File Provider Storage//My Awesome Image.png
That is on the slide from WWDC17 on the FileProvider extension, but I did not think it must follow that format so exactly.
这是在WWDC17的FileProvider扩展上的幻灯片上,但我认为它不一定必须遵循这种格式。
I had a directory not named "File Provider Storage" into which I was putting files directly, and startProvidingItem was never called. It was only when I made a directory for the uniqueFileID into which the file was placed, AND renamed my overall storage directory to "File Provider Storage" that startProvidingItem was called.
我有一个名为“文件提供程序存储”的目录,我直接放入文件,并且从未调用startProvidingItem。只有当我为放置文件的uniqueFileID创建目录时,才将我的整个存储目录重命名为“File Provider Storage”,调用startProvidingItem。
Also note that with iOS11, you'll need to provide a providePlaceholder call as well to the FileProviderExtension, use EXACTLY the code that is in the docs for that and do not deviate unless you are sure of what you are doing.
另请注意,对于iOS11,您还需要向FileProviderExtension提供一个providePlaceholder调用,请使用文档中的代码,并且不要偏离,除非您确定自己在做什么。