Sharing Extension in IOS8 beta

14,885

Solution 1

Below is how you can get the url. Notice the type identifier is kUTTypeURL and the block argument is NSURL. Also, the plist needs to be correct like mine also. The documentation was lacking and got help from number4 on the Apple dev forums. (you'll need to be registered and logged in to see it).

Code:

NSExtensionItem *item = self.extensionContext.inputItems.firstObject;
NSItemProvider *itemProvider = item.attachments.firstObject;
if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeURL]) {
    [itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeURL options:nil completionHandler:^(NSURL *url, NSError *error) {
        self.urlString = url.absoluteString;
    }];
}

Info.plist

<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>

Solution 2

I've solved it for myself. I was trying with Sharing Image.

- (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.

// Verify that we have a valid NSExtensionItem
NSExtensionItem *imageItem = [self.extensionContext.inputItems firstObject];
if(!imageItem){
    return;
}

// Verify that we have a valid NSItemProvider
NSItemProvider *imageItemProvider = [[imageItem attachments] firstObject];
if(!imageItemProvider){
    return;
}

// Look for an image inside the NSItemProvider
if([imageItemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage]){
    [imageItemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeImage options:nil completionHandler:^(UIImage *image, NSError *error) {
        if(image){
            NSLog(@"image %@", image);
            // do your stuff here...

        }
    }];
    }
// this line should not be here. Cos it's called before the block finishes.
// and this is why the console log or any other task won't work inside the block
[self.extensionContext completeRequestReturningItems:nil completionHandler:nil];

}

So what I did is just moved the [self.extensionContext completeRequestReturningItems:nil completionHandler:nil]; inside the block at the end of other tasks. The final working version look like this (Xcode 6 beta 5 on Mavericks OS X 10.9.4):

- (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.

// Verify that we have a valid NSExtensionItem
NSExtensionItem *imageItem = [self.extensionContext.inputItems firstObject];
if(!imageItem){
    return;
}

// Verify that we have a valid NSItemProvider
NSItemProvider *imageItemProvider = [[imageItem attachments] firstObject];
if(!imageItemProvider){
    return;
}

// Look for an image inside the NSItemProvider
if([imageItemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage]){
    [imageItemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeImage options:nil completionHandler:^(UIImage *image, NSError *error) {
        if(image){
            NSLog(@"image %@", image);
            // do your stuff here...

            // complete and return
            [self.extensionContext completeRequestReturningItems:nil completionHandler:nil];       
        }
    }];
    }
// this line should not be here. Cos it's called before the block finishes.
// and this is why the console log or any other task won't work inside the block
// [self.extensionContext completeRequestReturningItems:nil completionHandler:nil];

}

I hope it'll work for URL sharing as well.

Solution 3

Your extension view controller should be adopting the NSExtensionRequestHandling protocol. One of this protocol's methods is:

- (void)beginRequestWithExtensionContext:(NSExtensionContext *)context

You should be waiting for this to be called before you attempt to get the NSExtensionContext. It even provides the context in the method as the context parameter.

This was outlined in this document.

Solution 4

All of those previous answers are really good but I just came accross this issue in Swift and felt it was a little tidious to extract the URL from a given NSExtensionContext especially in the CFString to String conversion process and the fact that the completionHandler in loadItemForTypeIdentifier is not executed in the main thread.

import MobileCoreServices

extension NSExtensionContext {
  private var kTypeURL:String {
      get {
          return kUTTypeURL as NSString as String
      }
  }

  func extractURL(completion: ((url:NSURL?) -> Void)?) -> Void {
      var processed:Bool = false

      for item in self.inputItems ?? [] {
          if  let item = item as? NSExtensionItem,
              let attachments = item.attachments,
              let provider = attachments.first as? NSItemProvider
              where provider.hasItemConformingToTypeIdentifier(kTypeURL) == true {
                  provider.loadItemForTypeIdentifier(kTypeURL, options: nil, completionHandler: { (output, error) -> Void in
                      dispatch_async(dispatch_get_main_queue(), { () -> Void in
                          processed = true
                          if let url = output as? NSURL {
                              completion?(url: url)
                          }
                          else {
                              completion?(url: nil)
                          }
                      })

                  })
          }
      }

      // make sure the completion block is called even if no url could be extracted
      if (processed == false) {
          completion?(url: nil)
      }
  }
}

That way you can now simply use it like this in your UIViewController subclass:

self.extensionContext?.extractURL({ (url) -> Void in
    self.urlLabel.text = url?.absoluteString
    println(url?.absoluteString)
})

Solution 5

The other answers are all complicated and incomplete. They only work in Safari and do not work in Google Chrome. This works both in Google Chrome and Safari:

override func viewDidLoad() {
    super.viewDidLoad()

    for item in extensionContext!.inputItems {
        if let attachments = item.attachments {
            for itemProvider in attachments! {
                itemProvider.loadItemForTypeIdentifier("public.url", options: nil, completionHandler: { (object, error) -> Void in
                    if object != nil {
                        if let url = object as? NSURL {
                        print(url.absoluteString) //This is your URL
                        }
                    }
                })
            }
        }
    }
}
Share:
14,885
Masalis
Author by

Masalis

Updated on June 22, 2022

Comments

  • Masalis
    Masalis about 2 years

    I'm trying to create a sharing extension using the new iOS 8 app extensions. I tried to get the current URL of a Safari site to show it in a UILabel. Simple enough.

    I was working trough the official extension guide from apple here https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/Share.html#//apple_ref/doc/uid/TP40014214-CH12-SW1 but some things are not working as expected. I know it is only in beta but maybe I'm just doing something wrong.

    Here is my code to get the URL from safari inside the extensions ViewController:

    -(void)viewDidAppear:(BOOL)animated{
    NSExtensionContext *myExtensionContext = [self extensionContext];
    NSArray *inputItems = [myExtensionContext inputItems];
    NSMutableString* mutableString = [[NSMutableString alloc]init];
    for(NSExtensionItem* item in inputItems){
        NSMutableString* temp = [NSMutableString stringWithFormat:@"%@, %@, %lu, 
               %lu - ",item.attributedTitle,[item.attributedContentText string],
               (unsigned long)[item.userInfo count],[item.attachments count]];
    
        for(NSString* key in [item.userInfo allKeys]){
            NSArray* array = [item.userInfo objectForKey:@"NSExtensionItemAttachmentsKey"];
            [temp appendString:[NSString stringWithFormat:@" in array:%lu@",[array count]]];   
        }
        [mutableString appendString:temp];
    }
    self.myLabel.text = mutableString;
    }
    

    And this is the content of my Info.plist file of my Extension:

    <dict>
        <key>NSExtensionMainStoryboard</key>
        <string>MainInterface</string>
        <key>NSExtensionPointIdentifier</key>
        <string>com.apple.share-services</string>
        <key>NSExtensionActivationRule</key>
        <dict>
            <key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
            <integer>200</integer>
        </dict>
    </dict>
    

    When I visit apples iPod support page in Safari and try to share it to my extension, I get following values but no URL:

    item.attributedTitle = (null)
    item.attributedContentText = "iPod - Apple Support"
    item.userInfo.count = 2 (two keys: NSExtensionAttributedContentTextKey and
        NSExtensionItemAttachmentsKey)
    item.attachments.count = 0
    

    The arrays inside the objects of the dictionary are always empty.

    When I share the apple site with the system mail app the URL is posted to the message. So why is there no URL in my extension?

  • Masalis
    Masalis about 10 years
    Still not working. Even with the provided NSExtensionContext object, there is no URL.
  • abc123
    abc123 about 10 years
    You don't need Javascript, see my answer stackoverflow.com/a/24225678/1672161
  • IPv6
    IPv6 over 9 years
    Yes, it works, thank you! I also struggled with default ShareViewController, and noticed that any loadItemForTypeIdentifier in didSelectPost were simply ignored. but calling [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil]; after all blocks in loadItemForTypeIdentifier did the trick
  • Adnan
    Adnan over 9 years
    Cool. Good to hear that. :)
  • xZenon
    xZenon over 9 years
    I was never managed completionHandler to work properly for Share extension with no user interface. Ended up using the javascript based workaround - see my answer.
  • xZenon
    xZenon over 9 years
    Seems like Javascript approach is the only working way for Share extensions with no user interface. See my answer.
  • Dan Loughney
    Dan Loughney about 9 years
    Also, make sure you don't call [super didSelectPost] until you are done processing the item providers. It's implementation calls completeRequestReturningItems....
  • cdf1982
    cdf1982 almost 9 years
    Your solution looks very classy to me, I just got rid of the code I made myself in the last day and adopted yours. Thanks! I have a couple of question, if that's ok: 1. Would you call extractURL in isContentValid() or in viewDidAppear? isContentValid() looked like the right place to me, but that method is called every time the user types or deletes a character in the textfield of the extension... 2. Have you tried your code sharing from Pocket app? Both your extension & my previous code couldn't extract a url from items in Pocket list, while Messages or Mail extension can. Any idea why?
  • cdf1982
    cdf1982 almost 9 years
    Another note: retrieving only kUTTypeURL for me works only with some app, for others I had to add another where provider.hasItemConformingToTypeIdentifier() for "public.url". Still, doesn't work with Pocket app or Ebay app (of the 30 or so I've tried)
  • apouche
    apouche almost 9 years
    I had put that inside the viewDidAppear but you're right putting this in isContentValid() will trigger the code each time an update is made. I'm not sure there's a best choice here but I'll stick with viewDidAppear for now. As for the other apps I haven't tried with many of them. If they do not use the kUTTypeURL I guess you have dump the whole context to see what they return.
  • Esqarrouth
    Esqarrouth almost 9 years
    the swift code by tinkl looks to solve it with less code. is there any issues with that method?
  • Esqarrouth
    Esqarrouth almost 9 years
    this does not pull url from google chrome, any ideas why?
  • Esqarrouth
    Esqarrouth almost 9 years
    this does not pull url from google chrome, any ideas why?
  • Oren
    Oren over 8 years
    That shouldn't be necessary since the call to beginRequestWithExtensionContext happens before loadView developer.apple.com/library/prerelease/ios/documentation/…: