问题
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.
In Objective-C, this code and works and the block runs.
[itemProvider loadItemForTypeIdentifier:(@"public.url" options:nil completionHandler:^(NSURL *url, NSError *error) {
//My code
}];
In Swift, it looks very similar, however the closure doesn't run. Also, itemProvider.hasItemConformingToTypeIdentifier("public.url")
returns YES
so there must be a valid object to parse the url from inside the itemProvider
.
itemProvider.loadItemForTypeIdentifier("public.url", options: nil, completionHandler: { (urlItem, error) in
//My code
})
The Info.plist NSExtension portion is exactly the same for both Objective-C and Swift version and looks like this:
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
</dict>
<key>NSExtensionPointName</key>
<string>com.apple.share-services</string>
<key>NSExtensionPointVersion</key>
<string>1.0</string>
</dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
</dict>
What am I doing wrong?
回答1:
call
self.extensionContext!.completeRequestReturningItems([], completionHandler: nil)
at the end of completionHandler instead of calling it at the end of didSelectPost()
回答2:
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)
})
回答3:
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
回答4:
I had the same issue in my iOS 12.1. I'm calling
loadItemForTypeIdentifier:kUTTypeData
instead of kUTTypeImage
etc. It worked for me.
回答5:
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.
回答6:
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.
来源:https://stackoverflow.com/questions/24235954/ios-8-share-extension-loaditemfortypeidentifieroptionscompletionhandler-compl