I\'m using the loadItemForTypeIdentifier:options:completionHandler: method on an NSItemProvider object to extract a url from Safari via a Share extension in iOS 8.
call
self.extensionContext!.completeRequestReturningItems([], completionHandler: nil)
at the end of completionHandler instead of calling it at the end of didSelectPost()
I have been battled with this issue on and off for the last few weeks, and have finally found the issue. I has nothing to do with Objective C or Swift, it just appears to be a bug in Apple's code.
It seems that (as at iOS 8.0), the completion block is only called if you are using your own UIViewController
subclass. If you are using a subclass of SLComposeServiceViewController
, then the completion block is not called.
This is really annoying, as by default XCode creates you a ShareViewController
with a subclass of SLComposeServiceViewController
. To work around this issue, you just have to modify ShareViewController to inherit from UIViewController
. This will still give access to the extensionContext
property, but you'll obviously lose all of nice standard functionality and will have to implement your UI from scratch.
I've filed a radar with Apple, but have not had a reply yet. Hopefully this will be fixed in a future update.
I was never managed completionHandler to work properly for Share extension with no user interface (in such case extension's class is a subclass on NSObject).
Despite the [itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeURL]
returns YES
the completionHandler is never called both on the device or simulator.
After trying different approaches I ended up with workaround based on javascript passing URL back to extension (sorry as I use ObjC not Swift for my example).
Info.plist
NSExtension portion:
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
</dict>
<key>NSExtensionJavaScriptPreprocessingFile</key>
<string>Action</string>
</dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.services</string>
<key>NSExtensionPrincipalClass</key>
<string>ActionRequestHandler</string>
</dict>
Javascript Action.js
file:
var Action = function() {};
Action.prototype = {
run: function(arguments) {
arguments.completionFunction({ "currentURL" : window.location.href })
},
finalize: function(arguments) {
}
};
var ExtensionPreprocessingJS = new Action
ActionRequestHandler.h
header file:
@interface ActionRequestHandler : NSObject <NSExtensionRequestHandling>
@end
ActionRequestHandler.m
based on default Action extension template:
#import "ActionRequestHandler.h"
#import <MobileCoreServices/MobileCoreServices.h>
@interface ActionRequestHandler ()
@property (nonatomic, strong) NSExtensionContext *extensionContext;
@end
@implementation ActionRequestHandler
- (void)beginRequestWithExtensionContext:(NSExtensionContext *)context {
// Do not call super in an Action extension with no user interface
self.extensionContext = context;
BOOL found = NO;
// Find the item containing the results from the JavaScript preprocessing.
for (NSExtensionItem *item in self.extensionContext.inputItems) {
for (NSItemProvider *itemProvider in item.attachments) {
if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypePropertyList]) {
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypePropertyList options:nil completionHandler:^(NSDictionary *dictionary, NSError *error) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self itemLoadCompletedWithPreprocessingResults:dictionary[NSExtensionJavaScriptPreprocessingResultsKey]];
}];
}];
found = YES;
}
break;
}
if (found) {
break;
}
}
if (!found) {
// We did not find anything - signal that we're done
[self.extensionContext completeRequestReturningItems:@[] completionHandler:nil];
// Don't hold on to this after we finished with it
self.extensionContext = nil;
}
}
- (void)itemLoadCompletedWithPreprocessingResults:(NSDictionary *)javaScriptPreprocessingResults
{
// Get the URL
if ([javaScriptPreprocessingResults[@"currentURL"] length] != 0) {
NSLog(@"*** URL: %@", javaScriptPreprocessingResults[@"currentURL"]);
}
// Signal that we're done
[self.extensionContext completeRequestReturningItems:@[] completionHandler:nil];
// Don't hold on to this after we finished with it
self.extensionContext = nil;
}
@end
Hope it will help somebody to save couple of hours struggling with the completionHandler issue.
Since completeRequestReturningItems must be called after all completionHandlers are called back, below is what I do.
let group = dispatch_group_create()
for item: AnyObject in self.extensionContext!.inputItems {
let inputItem = item as! NSExtensionItem
for provider: AnyObject in inputItem.attachments! {
let itemProvider = provider as! NSItemProvider
if itemProvider.hasItemConformingToTypeIdentifier("public.url") {
dispatch_group_enter(group)
itemProvider.loadItemForTypeIdentifier("public.url", options: nil, completionHandler: {
(result: NSSecureCoding!, error: NSError!) -> Void in
//...
dispatch_group_leave(group)
});
}
if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeImage as String) {
dispatch_group_enter(group)
itemProvider.loadItemForTypeIdentifier(kUTTypeImage as String, options: nil, completionHandler: { (result, error) -> Void in
if let resultURL = result as? NSURL {
if let image = UIImage(data: NSData(contentsOfURL: resultURL)!) {
// ...
}
}
dispatch_group_leave(group)
});
}
}
}
dispatch_group_notify(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), {
self.extensionContext!.completeRequestReturningItems([], completionHandler: nil)
})
I had the same issue in my iOS 12.1. I'm calling
loadItemForTypeIdentifier:kUTTypeData
instead of kUTTypeImage
etc. It worked for me.
I take no credit for this, but have a look at how this guy did it: https://github.com/oguzbilgener/SendToInstapaper/blob/master/ShareExtension/SendingViewController.swift