Has anyone successfully implemented the "open" action for a file provider app extension? I've gotten as far as being able to read a file when the user initially selects the file in the document picker extension (essentially, this is the "import" action). But anything beyond that fails. Here are the issues I've run into:
- The app deadlocks if I use an
NSFileCoordinator
. If I save the URL and try to either read or write to it later, the call toThis works if I use bookmarks.startAccessingSecurityScopedResource
returnsNO
.If I tryThis works if I create the bookmarks inside the security scope.bookmarkDataWithOptions:
, I get back Error Domain=NSCocoaErrorDomain Code=260 "The operation couldn’t be completed. (Cocoa error 260.)".
Here is the template that is created for startProvidingItemAtURL:
when a file provider extension is created:
- (void)startProvidingItemAtURL:(NSURL *)url completionHandler:(void (^)(NSError *))completionHandler {
// Should ensure that the actual file is in the position returned by URLForItemWithIdentifier:, then call the completion handler
NSError* error = nil;
__block NSError* fileError = nil;
NSData * fileData = [NSData data];
// TODO: get the contents of file at <url> from model
[self.fileCoordinator coordinateWritingItemAtURL:url options:0 error:&error byAccessor:^(NSURL *newURL) {
[fileData writeToURL:newURL options:0 error:&fileError];
}];
if (error!=nil) {
completionHandler(error);
} else {
completionHandler(fileError);
}
}
But the extension deadlocks when I use the file coordinator. Also, the documentation for startProvidingItemAtURL:
says "Note
Do not use file coordination inside this method." so I've taken it out.
In the other app, this is what I am doing to read that file for the first time and then create a bookmark for it:
// Start accessing the security scoped resource.
[url startAccessingSecurityScopedResource];
void (^accessor)(NSURL *) = ^void(NSURL *url) {
// If the file is missing, create a default here. This really should be done inside
// the FileProvider method startProvidingItemAtURL:. Unfortunately, that method does
// not get called unless we use use the file coordinator, which can deadlock the app.
if (![url checkResourceIsReachableAndReturnError:nil]) {
// TODO: Create a real default file here.
[[NSFileManager defaultManager] createFileAtPath:url.path
contents:nil
attributes:nil];
}
// TODO: Do something with this file.
};
#ifdef USE_FILE_COORDINATOR
NSFileCoordinator *fileCoordinator = [NSFileCoordinator new];
[fileCoordinator coordinateReadingItemAtURL:url
options:NSFileCoordinatorReadingWithoutChanges
error:NULL
byAccessor:accessor];
#else
accessor(url);
#endif
// Store a bookmark for the url in the defaults so we can use it later.
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSError *error = nil;
NSURLBookmarkCreationOptions options = 0;
#ifdef NSURLBookmarkCreationWithSecurityScope
options |= NSURLBookmarkCreationWithSecurityScope;
#endif
NSData *bookmarkData = [url bookmarkDataWithOptions:options
includingResourceValuesForKeys:nil
relativeToURL:nil
error:&error];
if (error) {
NSLog(@"ERROR: %@", error);
}
[defaults setObject:bookmarkData forKey:@"BookmarkDataKey"];
// Stop accessing the security scoped resource.
[url stopAccessingSecurityScopedResource];
And finally, to use the bookmark later, I am doing the following:
// Get the bookmark from the defaults file.
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *bookmarkData = [defaults objectForKey:@"BookmarkDataKey"];
if (bookmarkData) {
// Convert the bookmark into a URL.
NSError *error;
BOOL bookmarkIsStale;
NSURLBookmarkResolutionOptions options = NSURLBookmarkResolutionWithoutUI;
#ifdef NSURLBookmarkResolutionWithSecurityScope
options |= NSURLBookmarkResolutionWithSecurityScope;
#endif
NSURL *url = [NSURL URLByResolvingBookmarkData:bookmarkData
options:options
relativeToURL:nil
bookmarkDataIsStale:&bookmarkIsStale
error:&error];
// Get the data from the URL.
BOOL securitySucceeded = [url startAccessingSecurityScopedResource];
if (securitySucceeded) {
NSString *message = [NSString stringWithFormat:@"Random number: #%d", arc4random() % 10000];
NSData *fileData = [NSKeyedArchiver archivedDataWithRootObject:message];
NSError *fileError = nil;
[fileData writeToURL:url options:0 error:&fileError];
[url stopAccessingSecurityScopedResource];
}
}
The second app also sometimes deadlocks if I use file coordination. So should I just not use file coordination in the second app as well? The problem is that if I don't use file coordination, then startProvidingItemAtURL:
in the File Provider extension never seems to get called.
Also, the documentation says to use NSURLBookmarkCreationWithSecurityScope
but this is undefined for iOS. The same goes for NSURLBookmarkResolutionWithSecurityScope
. Should I just use the OS X values or just not use them?