PIXEL
DOCK

I like the smell of Swift in the morning…

iOS8 Share extension – completionHandler for loadItemForTypeIdentifier: is never called

Posted: | Author: | Filed under: iOS, Swift | Tags: , | 5 Comments »

Creating a Share Extension in iOS8 is really easy:

  1. In your app choose File > New > Target
  2. Choose Application Extension > Share Extension
  3. Enter a name for your Share Extension and press Finish

That’s all! Xcode creates everything you need for you and puts it in it’s own folder. You’ll find a ShareViewController that is a subclass of SLComposeServiceViewController. That class’ UI looks a lot like the SLComposeViewController that you’ve probably worked with when using iOS’s Social Framework (to share your app’s content to Twitter or Facebook).

In ShareViewController Xcode creates some stubs for you where you have add the code to actually share the content to your service. In my case I want to build an extension for Safari that allows the user to share the page’s URL on my service. So when the user selects my share extension from Safari I need to retrieve the page’s URL to send it to my server.

When the user starts my Share Extension and presses the “Post” button the method didSelectPost is called on the ShareViewController.

Let’s have a look at the stub method that Xcode created:

- (void)didSelectPost {
    // This is called after the user selects Post. 
    // Do the upload of contentText and/or NSExtensionContext attachments.
    
    // Inform the host that we're done, so it un-blocks its UI. 
    // Note: Alternatively you could call super's -didSelectPost, 
    // which will similarly complete the extension context.
    [self.extensionContext completeRequestReturningItems:@[] 
                                       completionHandler:nil];
}

So far so good. So I added the code to retrieve the page URL. Compared to the easiness of creating the whole extension, the simple task of retrieving the URL takes quite some effort:

- (void)didSelectPost {
    NSExtensionItem *item = self.extensionContext.inputItems.firstObject;
    NSItemProvider *itemProvider = item.attachments.firstObject;
    if ([itemProvider hasItemConformingToTypeIdentifier:@"public.url"]) {
        [itemProvider loadItemForTypeIdentifier:@"public.url"
                                        options:nil
                              completionHandler:^(NSURL *url, NSError *error) {
                                  NSString *urlString = url.absoluteString;
                                  // send url to server to share the link
                                  // BUT THIS BLOCK IS NEVER EXECUTED!!!
                              }];
    }
    [self.extensionContext completeRequestReturningItems:@[] 
                                       completionHandler:nil];
}

That’s a lot of code for just an URL. And the worst thing: It’s not working! The completion handler of the loadItemForTypeIdentifier:options:completionHandler: is never called.

As it turns out calling completeRequestReturningItems:completionHandler: at the bottom of the method causes this problem. In Apple’s documentation it says that you are supposed to call this method to ‘Tell the host app to complete the app extension request with an array of result items’. Calling this methods dismisses the ShareViewController and deallocates it. So the NSItemProvider that contains the URL is also destroyed before it can access the URL.

So the solution is quite simple: The call to completeRequestReturningItems:completionHandler: has to be done AFTER retrieving the URL and sending it to the server. So it has to go into the completionHandler block:

- (void)didSelectPost {
    NSExtensionItem *item = self.extensionContext.inputItems.firstObject;
    NSItemProvider *itemProvider = item.attachments.firstObject;
    if ([itemProvider hasItemConformingToTypeIdentifier:@"public.url"]) {
        [itemProvider loadItemForTypeIdentifier:@"public.url"
                                        options:nil
                              completionHandler:^(NSURL *url, NSError *error) {
                                  NSString *urlString = url.absoluteString;
                                  // send url to server to share the link
                                  [self.extensionContext completeRequestReturningItems:@[]         
                                                                     completionHandler:nil];
                              }];
    }
}

And now the URL is retrieved and can be posted to my server.

In case you need to do this in Swift, here is how that would look like:

override func didSelectPost() {
    if let item = extensionContext?.inputItems.first as? NSExtensionItem {
        if let itemProvider = item.attachments?.first as? NSItemProvider {
            if itemProvider.hasItemConformingToTypeIdentifier("public.url") {
                itemProvider.loadItemForTypeIdentifier("public.url", options: nil, completionHandler: { (url, error) -> Void in
                    if let shareURL = url as? NSURL {
                        // send url to server to share the link
                    }
                    self.extensionContext?.completeRequestReturningItems([], completionHandler:nil)
                })
            }
        }
    }
}

5 Comments

  • 1

    Jessesaid at

    Very very helpful and makes total sense! Thank You!

  • 2

    JSWORLDsaid at

    very helpful too! really thank you!

  • 3

    Marcus Rsaid at

    Was pulling out my hair. Thank you so much.

  • 4

    TimZsaid at

    Was also pulling out my hair. Thanks bro.

  • 5

    Gregsaid at

    I come form the future and this still is an issue in iOS11.

    Thank you for your help!!


Leave a Reply