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.
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
- Step 1: Get the attachments
- Step 2: Copy the attachments for the AIR app to access
- Step 3: Start your AIR app
- Step 4: Add URL scheme to your AIR app
- Step 5: Process the shared files in AIR
- Bonus step: Don’t want to show the Post dialog?
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:
- Part 1: What are iOS Extensions?
- Part 2: How to list an AIR app in the iOS Share Menu
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:
We are interested in the implementation of ShareViewController.m, the meat of which looks like this out of the box:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@implementation ShareViewController - (BOOL)isContentValid { // Do validation of contentText and/or NSExtensionContext attachments here return YES; } - (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]; } - (NSArray *)configurationItems { // To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here. return @[]; } @end |
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.
First, let’s add a couple of private members to the ShareViewController class (add these right under
@implementation ShareViewController):
1 2 |
NSUInteger m_inputItemCount = 0; // Keeps track of the number of attachments we have opened asynchronously. NSString * m_invokeArgs = NULL; // A string to be passed to your AIR app with information about the attachments. |
Next, modify didSelectPost like this:
1 2 3 4 5 6 7 |
- ( void ) didSelectPost { [ self passSelectedItemsToApp ]; // Note: This call is expected to be made here. Ignore it. We'll tell the host we are done after we've invoked the app. // [ self.extensionContext completeRequestReturningItems: @[] completionHandler: nil ]; } |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
- ( void ) passSelectedItemsToApp { NSExtensionItem * item = self.extensionContext.inputItems.firstObject; // Reset the counter and the argument list for invoking the app: m_invokeArgs = NULL; m_inputItemCount = item.attachments.count; // Iterate through the attached files for ( NSItemProvider * itemProvider in item.attachments ) { // Check if we are sharing a JPEG if ( [ itemProvider hasItemConformingToTypeIdentifier: ( NSString * ) kUTTypeJPEG ] ) { // Load it, so we can get the path to it [ itemProvider loadItemForTypeIdentifier: ( NSString * ) kUTTypeJPEG options: NULL completionHandler: ^ ( UIImage * image, NSError * error ) { static int itemIdx = 0; if ( NULL != error ) { NSLog( @"There was an error retrieving the attachments: %@", error ); return; } // The app won't be able to access the images by path directly in the Camera Roll folder, // so we temporary copy them to a folder which both the extension and the app can access: NSString * filePath = [ self saveImageToAppGroupFolder: image imageIndex: itemIdx ]; // Now add the path to the list of arguments we'll pass to the app: [ self addImagePathToArgumentList: filePath ]; // If we have reached the last attachment, it's time to hand control to the app: if ( ++itemIdx >= m_inputItemCount ) { [ self invokeApp: m_invokeArgs ]; } } ]; } } } |
There are several things to note here:
- You can access the attachments (shared files) by iterating the attachments property of NSExtensionItem. To get hold of an NSExtensionItem, you go via NSExtensionContext, which, much like the extension context in an ANE, is how the host app (in this case Camera Roll) and the iOS extension communicate.
- 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
- ( void ) addImagePathToArgumentList: ( NSString * ) imagePath { assert( NULL != imagePath ); // The list of arguments we will pass to the AIR app when we invoke it. // It will be a comma-separated list of file paths: /path/to/image1.jpg,/path/to/image2.jpg if ( NULL == m_invokeArgs ) { m_invokeArgs = imagePath; } else { m_invokeArgs = [ NSString stringWithFormat: @"%@,%@", m_invokeArgs, imagePath ]; } } |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
- ( NSString * ) saveImageToAppGroupFolder: ( UIImage * ) image imageIndex: ( int ) imageIndex { assert( NULL != image ); NSData * jpegData = UIImageJPEGRepresentation( image, 1.0 ); NSURL * containerURL = [ [ NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier: APP_SHARE_GROUP ]; NSString * documentsPath = containerURL.path; // Note that we aren't using massively unique names for the files in this example: NSString * fileName = [ NSString stringWithFormat: @"image%d.jpg", imageIndex ]; NSString * filePath = [ documentsPath stringByAppendingPathComponent: fileName ]; [ jpegData writeToFile: filePath atomically: YES ]; return filePath; } |
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:
1 |
NSString * APP_SHARE_GROUP = @"group.com.diadraw.AIRApp.sharing"; |
Step 3: Start your AIR app
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:
1 2 3 4 5 |
[ self.extensionContext openURL: yourAppCustomURL completionHandler: ^( BOOL success ) { // deal with success or failure here } ]; |
- 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
- ( void ) invokeApp: ( NSString * ) invokeArgs { // Prepare the URL request // this will use the custom url scheme of your app // and the paths to the photos you want to share: NSString * urlString = [ NSString stringWithFormat: @"%@://%@", APP_SHARE_URL_SCHEME, ( NULL == invokeArgs ? @"" : invokeArgs ) ]; NSURL * url = [ NSURL URLWithString: urlString ]; NSString *className = @"UIApplication"; if ( NSClassFromString( className ) ) { id object = [ NSClassFromString( className ) performSelector: @selector( sharedApplication ) ]; [ object performSelector: @selector( openURL: ) withObject: url ]; } // Now let the host app know we are done, so that it unblocks its UI: [ super didSelectPost ]; } |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
- ( void ) invokeApp: ( NSString * ) invokeArgs { // As we'll be creating UI, make sure the calls happen on the main thread: dispatch_async ( dispatch_get_main_queue(), ^{ // openURL doesn't work for any extension, but the Today one, // so we'll use a workaround: // 1. Instantiate a UIWebView that's so tiny it won't show on the screen: UIWebView * webView = [ [ UIWebView alloc ] initWithFrame: CGRectMake( 0, 0, 0, 0 ) ]; // 2. Prepare the URL request for the UIWebView: // this will use the custom url scheme of your app // and the paths to the photos you want to share: NSString * urlString = [ NSString stringWithFormat: @"%@://%@", APP_SHARE_URL_SCHEME, ( NULL == invokeArgs ? @"" : invokeArgs ) ]; NSURL * url = [ NSURL URLWithString: urlString ]; NSURLRequest * request = [ NSURLRequest requestWithURL: url ]; // 3. Now get the UIWebView to load the request, which will launch the app: [ webView loadRequest: request ]; [ self.view addSubview: webView ]; // 4. And finally, say bye to the UIWebView. // The delay is important here, otherwise the UIWebView won't have time // to invoke your app before it's dismissed: [ webView performSelector: @selector( removeFromSuperview ) withObject: NULL afterDelay: 2.0 ]; // Now let the host app know we are done, so that it unblocks its UI: [ super didSelectPost ]; }); } |
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:
1 |
[ super didSelectPost ]; |
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:
1 |
const NSString * APP_SHARE_URL_SCHEME = @"diadrawairapp"; |
Step 4: Add URL scheme to your AIR app
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):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<InfoAdditions><![CDATA[ <key>UIDeviceFamily</key> <array> <string>1</string> <string>2</string> </array> <key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleURLSchemes</key> <array> <string>diadrawairapp</string> </array> </dict> </array> ]]></InfoAdditions> |
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:
1 2 3 4 |
<s:VGroup x="0" y="0" width="100%" height="100%"> <s:Image id="m_image" width="100%" height="100%"/> <s:TextArea id="m_log" width="100%" height="50%"/> </s:VGroup> |
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:
1 2 3 4 5 |
protected function creationCompleteHandler( event : FlexEvent ) : void { // Start listening to InvokeEvent, where the Share Extension will pass us information: NativeApplication.nativeApplication.addEventListener( InvokeEvent.INVOKE, onInvokeEvent ); } |
And the event handler function, which will receive the arguments we passed to the app:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
protected function onInvokeEvent( _event : InvokeEvent ) : void { if ( 0 == _event.arguments.length ) { // The app was probably invoked for another reason and not by the Share Extension log( "Nothing to process" ); return; } // Get the paths to the files the user wants to share: var fileList : Array = processInvokeArguments( _event.arguments.toString() ); if ( null == fileList || 0 == fileList.length ) { log( "No files were found in the argument list" ); return; } // Let's preview the first image // (we are only dealing with JPEGs in this example): previewImage( "file://" + fileList[ 0 ] ); } |
Next comes processInvokeArguments, which takes care of parsing the string with which the app was invoked:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
private function processInvokeArguments( _argStr : String ) : Array { // We expect a single argument in the following format: // diadrawairapp:///private/var/mobile/Containers/Shared/AppGroup/D4A4F193-4125-406F-9A85-2B301F233D7E/image0.jpg,/private/var/mobile/Containers/Shared/AppGroup/D4A4F193-4125-406F-9A85-2B301F233D7E/image1.jpg log( "Invoke arguments: " + _argStr ); if ( -1 == _argStr.indexOf( APP_SHARE_URL_SCHEME ) ) { // The app was probably invoked for another reason and with another URL scheme, get out of here. return null; } // Parse the argument string. // a) remove the url scheme: var argList : Array = _argStr.split( "://" ); // We expect the string to be split into exactly two parts: // "diadrawairapp" // and "/private/var/mobile/Containers/Shared/AppGroup/D4A4F193-4125-406F-9A85-2B301F233D7E/image0.jpg,/private/var/mobile/Containers/Shared/AppGroup/D4A4F193-4125-406F-9A85-2B301F233D7E/image1.jpg" if ( 2 > argList.length ) { log( "No arguments after the url scheme" ); return null; } // b) We should now be left with a comma separated list of file paths like this: // private/var/mobile/Containers/Shared/AppGroup/D4A4F193-4125-406F-9A85-2B301F233D7E/image0.jpg,/private/var/mobile/Containers/Shared/AppGroup/D4A4F193-4125-406F-9A85-2B301F233D7E/image1.jpg var fileList : Array = argList[ 1 ].split( "," ); log ( "\n\nFile list: " ); for each ( var filePath : String in fileList ) { log( "\n" + filePath ); } return fileList; } |
And, finally, the two auxiliary functions that the code above calls – for previewing an image:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
private function previewImage( _pathToImage : String ) : void { var onFileLoaded : Function = function( _event : Event ) : void { var loader : Loader = Loader( _event.target.loader ); var bm : Bitmap = Bitmap( loader.content ); m_image.graphics.clear(); m_image.source = bm; log( "Loaded file " + _pathToImage ); } var onFileLoadError : Function = function( _event : IOErrorEvent ):void { log( "Could not load image: " + _event.toString() ); } log( "Loading file " + _pathToImage ); var loader : Loader = new Loader(); loader.contentLoaderInfo.addEventListener( Event.COMPLETE, onFileLoaded ); loader.contentLoaderInfo.addEventListener( IOErrorEvent.IO_ERROR, onFileLoadError ); var context : LoaderContext = new LoaderContext(); context.checkPolicyFile = true; var fileRequest : URLRequest = new URLRequest( _pathToImage ); loader.load( fileRequest, context ); } |
and for logging what’s going on:
1 2 3 4 5 |
private function log( _msg : String ) : void { m_log.text += "\n" + _msg + "\n"; trace( _msg ); } |
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:
1 |
#define HIDE_POST_DIALOG |
Now, instead of gathering the information about attachments in didSelectPost, override configurationItems to do it for you:
1 2 3 4 5 6 7 8 9 10 |
#ifdef HIDE_POST_DIALOG - ( NSArray * ) configurationItems { // Comment out this whole function if you want the Post dialog to show. [ self passSelectedItemsToApp ]; // To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here. return @[]; } #endif |
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:
1 2 3 4 5 6 7 8 9 10 11 |
- ( void ) didSelectPost { #ifdef HIDE_POST_DIALOG return; #endif [ self passSelectedItemsToApp ]; // Note: This call is expected to be made here. Ignore it. We'll tell the host we are done after we've invoked the app. // [ self.extensionContext completeRequestReturningItems: @[] completionHandler: nil ]; } |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#ifdef HIDE_POST_DIALOG - ( void ) willMoveToParentViewController: ( UIViewController * ) parent { // This is called at the point where the Post dialog is about to be shown. // Make it transparent, so we don't see it, but first remember how transparent it was originally: m_oldAlpha = [ self.view alpha ]; [ self.view setAlpha: 0.0 ]; } #endif #ifdef HIDE_POST_DIALOG - ( void ) didMoveToParentViewController: ( UIViewController * ) parent { // Restore the original transparency: [ self.view setAlpha: m_oldAlpha ]; } #endif |
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.
1 |
CGFloat m_oldAlpha = 1.0; // Keeps the original transparency of the Post dialog for when we want to hide it. |
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):
1 2 3 4 5 6 7 8 9 10 11 12 |
#ifdef HIDE_POST_DIALOG - ( id ) init { if ( self = [ super init ] ) { // Subscribe to the notification which will tell us when the keyboard is about to pop up: [ [ NSNotificationCenter defaultCenter ] addObserver: self selector: @selector( keyboardWillShow: ) name: UIKeyboardWillShowNotification object: nil ]; } return self; } #endif |
Then, add a method that will handle this notification:
1 2 3 4 5 6 7 |
#ifdef HIDE_POST_DIALOG - ( void ) keyboardWillShow: ( NSNotification * ) note { // Dismiss the keyboard before it has had a chance to show up: [ self.view endEditing: true ]; } #endif |
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:
1 |
// #define HIDE_POST_DIALOG |
Leaving it in will hide the dialogue: i.e. all the code that does the hiding will be compiled and run.
Thank you, Tim!
Bertrand
This does not work with iOS 8.3.
Radoslava
Hi Bertrand,
What are the symptoms you are seeing?
Nick
Looks great! Does this work for all iOS 8 versions? (8.3 and the new 8.4)
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
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
Tim
about your hack: Could this help to prevent the keyboard from poping up?:
http://stackoverflow.com/questions/18755410/how-to-dismiss-keyboard-ios-programmatically
Did you try it out yet to close it directly?
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.
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?
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.
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
Vrashabh Irde
Yes, that helped thanks. One question I have is whether this piece of code, was it accepted on the app store?
Thanks
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
Vladimir
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
Bjørn
Is there an equivalent swift solution?
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
Android Example
Hi dude, i have also find out one good example
Show Loader To Open Url In WebView
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.
Arti
Had the same issue, but that was because I forgot to set the bundle identifier matching the same app id in Air.
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.
Radoslava
Hi guys, thank you for your patience! And thanks, Arti and Nilesh for helping out!
Android Example
Hi dude, i have also find out one good example
Show Loader To Open Url In WebView
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
pol2095
Thanks for ignoring my last comment, the problem didn’t come from there.