How to launch your app from the iOS 8 Share Menu – updated for iOS 8.4

In this part of the iOS 8 Share Menu tutorial you get to add some code and launch your app from the Share Menu. Concretely, our example lets you select one or more images form Camera Roll and launch your app, passing information about the shared files.

The source code for this tutorial, complete with build scripts, is available here.

Opt In Image
Early bird offer on the ANE eBooks

 

Buy any Easy Native Extensions 2nd Edition package and get our $99 iOS + Android ANE Template completely free before the end of June 2015.

 

 

  • step-by-step guide to making your iOS extension in under an hour
  • library for data conversion between ActionScript and native code
  • tutorials
  • infographics
  • code included

Time

20-25 minutes

Wait, have you done this first?

To do this part of the tutorial you will need:

  • an AIR app
  • an iOS Extension (plugin) that lists your AIR app in the iOS 8 share menu

If you haven’t got them yet, you can follow the first two parts of this tutorial:

or download the source code here.

Step 1: Get the attachments

The attachments are the files that the user wants to share.

In your Xcode project open your iOS share extension (we called it AIRAppSharePlugin). It should have the following structure:

share plugin project structure

We are interested in the implementation of ShareViewController.m, the meat of which looks like this out of the box:

We will leave isContentValid and configurationItems alone and will add code to didSelectPost, which will get information about the shared files that can then be passed on to your AIR app.

didSelectPost is triggered when the user taps Post in the share dialog that the extension pops up and this is when we have information about the items being shared.

the post dialog

Tip: Don’t want the Post dialog to pop up? Check out the bonus step at the end of this article for how to show your app directly and bypass the dialog.

First, let’s add a couple of private members to the ShareViewController class (add these right under

@implementation ShareViewController):

Next, modify didSelectPost like this:

Here you make a call to a function which will gather information about the file(s) you want to share and will start your app, passing that information to it. Let us define that function:

There are several things to note here:

  • NSExtensionItem‘s attachment property is an array of NSItemProvider objects. NSItemProvider’s loadItemForTypeIdentifier is how you can get information about an attachment: from its path (if it’s a file on disk) to all of its data. loadItemForTypeIdentifier presents its results asynchronously in the completionHandler you see on line 18 above.
    Tip: To get the direct path to a shared file, use the following completion handler instead: completionHandler: ^ ( NSURL * url, NSError * error ).
  • Having the path to an image in Camera Roll will not be of much use to our AIR app, as it won’t have permission to access the media folder directly. This is why we go through the more tedious task of loading the whole image and then saving it to a place the AIR app can access. You will do this in Step 2.
  • The call that was initially done in didSelectPost, completeRequestReturningItems (or its alternative [ super didSelectPost ]; ) is no longer your friend, so we’ve commented it out. What completeRequestReturningItems does is pass UI control back to the host app (Camera Roll) and you want to hold on to that control for a little longer, so you can use a trick to launch our AIR app. Not too long, though, as the host app’s UI will be blocked until you make one of these calls to let it know that the extension has done its job. You will instead let the host app know we’re done in invokeApp, which you will implement in Step 3.
  • This example shows you how to share JPEG attachments. For other types of information you will need to modify the code in didSelectPost to first check if the desired type has been selected and then to handle it. For a full list of type constants follow this link.

You have probably noticed that the code above calls a couple of functions that have not been defined yet: saveImageToAppGroupFolder, addImagePathToArgumentList and invokeApp. You will add them one by one, starting with addImagePathToArgumentList:

Step 2: Copy the attachments for the AIR app to access

As mentioned in the previous step, your AIR app can’t access files in Camera Roll by their path. So let’s have the extension make temporary copies of them in a folder which both extension and app can get to:

Now define APP_SHARE_GROUP, which is the nebulous place which extension and app can share – add it as another private member to the ShareViewController class:

Note: For this to work you will need to create an App Group. This involves special provisioning for your app. The next part of the tutorial shows you how to do that: Sharing files between an AIR app and an iOS extension.

Step 3: Start your AIR app

Disclaimer: The ways shown below of starting your AIR app are a bit unconventional and we can’t guarantee that Apple will approve your app for submission in the App Store.

Once you have collected information about the shared files, you can start your AIR app and pass this information.

I have good news and bad news here:

  • the good news first: the iOS SDK provides an easy way to invoke your app from an iOS extension, using a custom URL scheme: 
  • now the bad news: at the moment this doesn’t work for the Share extension or for any other extension, other than the Today one.

But hey, I have more good news for you: there are a couple of workarounds.

Workaround for iOS 8.3 and 8.4: 

Get the enclosing application to open the URL. Credits go to Hank Brekke who posted a version of this workaround on StackOverflow.

 

Workaround for older iOS versions (prior to 8.3):

Load the URL in an UIWebView as a workaround. This is why you need to hold on to your control over the UI before releasing it back to the host app. I would like to credit Julio Bailon’s example on StackOverflow.

As mentioned in Step 1, the host app’s UI (Camera Roll in this case) is blocked and unresponsive while you are busy dealing with file attachments and app launching. The last call in the code above:

lets the host app know that you are done, so it can go back to life.

Now define the string that will serve as your app’s custom URL scheme, used by invokeApp. Put this with the other private declarations under @implementation ShareViewController:

Step 4: Add URL scheme to your AIR app

A URL scheme is a string that is defined in your app descriptor (or Info.plist) and enables other apps to open your app as if opening a URL. The URL for your app will look like “your_app_url_scheme://”, followed by any number of arguments that are passed to the app at launch.

To add a URL scheme for your AIR app, define the CFBundleURLTypes and CFBundleURLScheme keys in the iPhone.InfoAdditions section of your AIR app descriptor (your-app-name-app.xml file):

Note that the URL scheme here uses the same string we use in invokeApp in the iOS extension.

Step 5: Process the shared files in AIR

As your AIR app may be different, for the code below to make sense, I should tell you that my app uses two UI elements: a spark Image, called m_image,  for previewing one of the shared photos and a TextArea, called m_log, for logging what’s going on:

image preview in the AIR app

With that ground work laid out, let us add a listener for when the app is started (invoked). I have added this one to the main view’s creationComplete handler:

And the event handler function, which will receive the arguments we passed to the app:

Next comes processInvokeArguments, which takes care of parsing the string with which the app was invoked:

And, finally, the two auxiliary functions that the code above calls – for previewing an image:

and for logging what’s going on:

Note: Your work is not quite done yet. For the file passing to work you will need to create an App Group and provision your app for that. The next part of the tutorial shows you how to do that: Sharing files between an AIR app and an iOS extension

Note that the URL scheme here uses the same string we use in invokeApp in the iOS extension.

Bonus step: Don’t want to show the Post dialog?

If you want to launch your app directly, without going via the Post dialog, you will need to add a few more lines of code. For debugging purposes it will be useful to be able to turn this option on and off, so let us start by defining a macro at the top of the file (right underneath the #import statements). This will let you conditionally compile the code with or without hiding the Post dialog:

Now, instead of gathering the information about attachments in didSelectPost, override configurationItems to do it for you:

Note that we’ve wrapped this whole function with an #ifdef statement: this means that if the HIDE_POST_DIALOG macro is defined, the code inside the #ifdef will be compiled, otherwise the compiler will ignore it, as if it does not exist.

Next, modify didSelectPost, so that it doesn’t do the same work twice:

Here the #ifdef statement only wraps one line: if the HIDE_POST_DIALOG macro is defined, didSelectPost will reach the return statement and not do anything else. Otherwise the line with return; will be ignored by the compiler and didSelectPost will run the rest of its code.

And then override the following two methods of ShareViewController to hide the Post dialog from the user, both of these wrapped in #ifdefs:

The willMoveToParentViewController method is called automatically (i.e. not by you) when the Post dialog is about to show up on the screen. At that point you sneakily make it transparent, so that it is not visible on the screen. For the sake of playing nice however, you first make a note of what its original transparency was and then restore it in didMoveToParentViewController.

Now let us define m_oldAlpha: this is a member variable of ShareViewController, so put the definition inside the  @implementation ShareViewController/@end block, but not inside a method. Best to stick it right after where you defined m_inputItemCount and m_invokeArgs earlier.

This is a bit hacky and not ideal, as you still see the keyboard briefly popping up before your app is invoked. More elegant suggestions are welcome: share yours with the community in the comments below.

Update, 13 July 2015

Here is a suggestion from Tim for dismissing the keyboard.

We will do that at the point when the keyboard tells us it’s about to pop up, so first subscribe to UIKeyboardWillShowNotification. For that purpose let us override ShareViewController‘s constructor (i.e. this needs to be inside the  @implementation ShareViewController/@end block):

Then, add a method that will handle this notification:

Note that these two methods are also conditionally compiled (which is what the #ifdef checks are for). Now, if you want the Post dialog to show up, just comment out the macro definition you added above:

Leaving it in will hide the dialogue: i.e. all the code that does the hiding will be compiled and run.

Thank you, Tim!

The source code for this tutorial, complete with build scriptsis available here.

What’s next?


Warning: count(): Parameter must be an array or an object that implements Countable in /home/easyna6/public_html/easynativeextensions_wp/wp-includes/class-wp-comment-query.php on line 399

Comments

    • Radoslava

      Hi Nick,

      iOS 8.3 and 8.4 need a different way of launching the app and we have updated Step 3 to include that.
      Hope you find it useful,
      Radoslava

  1. Normen

    Fantastic tutorial, thanks!
    How about sharing text instead of an image, is that possible to? Would be great to share some text from an article in Safari or an URL to a post.

    Keep up the great work :-)

    • Radoslava

      Hi Normen,

      For text you can add a check for the exact text type, so instead of this:
      if ( [ itemProvider hasItemConformingToTypeIdentifier: ( NSString * ) kUTTypeJPEG ] )
      you check against one of the text type constants.
      And then add a handler for that after the check.

      Cheers,
      Radoslava

  2. Radoslava

    Thanks for the suggestion, Tim!

    I added an update above based on it. Instead of waiting until the keyboard is already on the screen however, the updated code dismisses it before it has had a chance to show up.

  3. Tim

    App crashes if you try to open an image for the second time. Maybe because it’s copied to the temp folder and already in there or because the app runs in the background?

    A suggestion: It would be helpful if you could post the complete code of ViewController.m in one code box – I have no idea where to add the “keyboardWillShow” update – that would help me finding it out. :-)

    • Radoslava

      Well spotted. And thanks for the steps to reproduce it, Tim!
      The issue wasn’t actually with how and where files were copied, but rather with this innocently looking statement:
      [ self removeFromParentViewController ]; inside didMoveToParentViewController. Looks like it would permanently sever the tie between parent and child and the second time we tried to share a file, our extension wouldn’t get some of the calls we rely on.

      We’ve updated the tutorial code above as well as the downloadable code.

    • Christian

      Hi Tim,
      Is your post also available in swift 2.0? Do you know is it possible to open the app without the POST window in swift?

  4. Vrashabh

    Hi,
    Thanks for the wonderful article, for a noob iOS developer for me, and coming from Android, this is fantastic! :)
    One question I have is, I am basically trying to send the path of the file back to the application (IOS 8.4) openURL from the extension, but it doesn’t seem to go there. Everything else works well. Any ideas on that? Even with the workaround suggested here.

  5. Vrashabh

    And what should the APP_SHARE_URL_SCHEME value be. In general it would be myapp I am assuming, but is there a place where I need to declare that? That’d be like an intent-filter in Android

    • Radoslava

      Hi Vrashabh,

      Great that you are finding the tutorial helpful.

      On your question about passing the file path back to the app: can you determine where the path gets lost: do you get a valid string at the point of reading the path or does passing this string back to AIR mess it up? You can use the last step of the tutorial, How to debug your iOS extension to put a breakpoint where you get the file path and see what the string looks like.

      On your question about APP_SHARE_URL_SCHEME: this is a string that’s defined in a couple of places. You decide the value of the string – most commonly it will be your app name.

      The first place is in the code of the extension (the very end of Step 3 on this page) – this is mostly for convenience.

      The same string (not the constant) is then put in the AIR app descriptor under CFBundleURLSchemes (Step 4) – this is where it should be for iOS to find it.

      I hope this helps,
      Radoslava

        • Radoslava

          The authors of both workarounds for showing the app, credited above, say that their apps were accepted in the App Store. We haven’t had to submit an AIR app using this, though, so can’t guarantee how these are treated.

          It would be helpful if you shared your experience.
          Thanks,
          Radoslava

  6. Hi Radoslava,

    I was able to open my objective c app from share extension, following your wonderful tutorial.

    Could you suggest how I should handle opening my app with the filepath passed to it, instead of your (flex?) AIR app.

    Thanks.

    • Radoslava

      Hi Vladimir,

      Glad the tutorial has worked for you.
      On passing the file path: the same code should work regardless of whether you are passing the string to an AIR or to a native app.

      Cheers,
      Radoslava

    • Hristo

      Hi Bjorn,

      Thank you for the question.
      We are working on a series of tutorials and a book about migrating from ActionScript to Swift at the moment.
      The Swift version of this tutorial is one of the topics covered in it.

      Out of curiosity: what kind of app you are working on?
      Feel free to drop us a line at office@diadraw.com.

      Cheers,
      Hristo

  7. Nicolas

    Hi Radoslava,

    Thanks for that great tutorial, it helped me a lot at some point, but it seems I cannot manage to make it work correctly.
    I can see my AIR app icon in applications list when I hit the Share button, but the popup does not show up.
    I checked the logs from the iPad I’m working on and I get this:


    >>>>> pkd[90] : assigning plug-in SHARE_EXTENSION_BUNDLE_ID(1.0) to plugin sandbox
    >>>>> pkd[90] : enabling pid=2715 for plug-in SHARE_EXTENSION_BUNDLE_ID(1.0) 3FB966EB-9553-48A8-A4A1-F47C11801C57 /private/var/containers/Bundle/Application/0271BE52-BAB6-486D-9974-C10E0F127B01/MyApp.app/PlugIns/ShareExtension.appex
    >>>>> kernel[0] : xpcproxy[3728] Container: /private/var/mobile/Containers/Data/PluginKitPlugin/6D6D8B9D-9FCB-474C-9235-5D62F6264F18 (sandbox)
    >>>>> com.apple.xpc.launchd[1] (SHARE_EXTENSION_BUNDLE_ID[3728]) : Service exited due to signal: Trace/BPT trap: 5
    >>>>> MobileSlideShow[2715] : plugin SHARE_EXTENSION_BUNDLE_ID interrupted
    >>>>> MobileSlideShow[2715] : Hub connection error Error Domain=NSCocoaErrorDomain Code=4097 “connection to service named SHARE_EXTENSION_BUNDLE_ID” UserInfo={NSDebugDescription=connection to service named SHARE_EXTENSION_BUNDLE_ID}
    >>>>> MobileSlideShow[2715] : 2016-07-20 14:03:07.766|MobileSlideShow|0x15f6a04c0: HOST: Failed to load remote view controller with error: Error Domain=NSCocoaErrorDomain Code=4097 “connection to service named SHARE_EXTENSION_BUNDLE_ID” UserInfo={NSDebugDescription=connection to service named SHARE_EXTENSION_BUNDLE_ID}
    >>>>> MobileSlideShow[2715] : 2016-07-20 14:03:07.767|MobileSlideShow|0x15f6a04c0: Sheet not being presented, calling premature completion
    >>>>> ReportCrash[3729] : Formulating report for corpse[3728] ShareExtension

    Did you ever meet this problem?
    Do you think this could be something with the provisioning?
    Just so you know, I did code in Swift on Xcode 7.1.

    Thanks.

  8. Nilesh

    We had same issue, our issue was strange. Same application was working ios 9 device but not in ios 10 device.

    So we have released app using Xcode 8 and issue seems fixed for us.

  9. pol2095

    Hello, I have a problem on iOS 12.1 :
    [ itemProvider loadItemForTypeIdentifier: ( NSString * ) kUTTypeJPEG
    options: NULL
    completionHandler: ^ ( UIImage * image, NSError * error )
    {
    NSLog( @"Test" );

    NSLog( @”Test” ) is never displayed
    Thanks

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">