Share Attachment from Mail App with Share Extension in iOS

Share attachment from Mail App with Share extension in iOS

So finally I found an answer on my question! Just in case if somebody will meet the same problem..
First of all I have to use PREDICATE statement (Subquery) in Info.plist instead of key NSExtensionActivationSupportsAttachmentsWithMaxCount. Like:

        <key>NSExtensionActivationRule</key>
<string>SUBQUERY (
extensionItems,
$extensionItem,
SUBQUERY (
$extensionItem.attachments,
$attachment,
(
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "com.adobe.pdf"
|| ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.image"
|| ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.plain-text"
|| ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.png"
|| ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.jpeg"
|| ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.jpeg-2000"
|| ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.tiff"
|| ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "com.compuserve.gif"
|| ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "com.microsoft.bmp"
|| ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "com.microsoft.word.doc"
)
).@count == 1 // Important! to activate extension only on 1 chosen image
).@count == 1
</string>

Second: Properly get all attachments using necessary TypeIdentifier (UTI):

    if let content = extensionContext!.inputItems.first as? NSExtensionItem {
if let contents = content.attachments as? [NSItemProvider] {
for attachment in contents{
attachment.loadItemForTypeIdentifier("public.item", options: nil) { data, error in
let url = data as! NSURL
let fileExtension = url.pathExtension as String!
let fileName = self.generateImageName() as String
if let fileData = NSData(contentsOfURL: url) {
self.uploadFile("\(fileName).\(fileExtension)", data: fileData)
}
}
}
}
}

"public.item" - is universal UTI to support all kind of file extensions listed in your NSExtensionActivationRule string.
You can get necessary UTI on https://developer.apple.com

Good Luck with developing of action extensions! Any questions are welcome!

Code to share file path/file between a share extension and iOS app

UPDATE

The app was accepted in the review using NSUSERDEFAULTS as the message passing mechanism. Here are the steps.

1.) Share extension:

#import "ShareViewController.h"
#import <MobileCoreServices/UTCoreTypes.h>
//Macro to hide post dialog or not, if defined, will be hidden, comment during debugging
#define HIDE_POST_DIALOG

@interface ShareViewController ()

@end

@implementation ShareViewController

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.
NSString * APP_SHARE_GROUP = @"group.com.schemename.nameofyourshareappgroup";
const NSString * APP_SHARE_URL_SCHEME = @"schemename";
CGFloat m_oldAlpha = 1.0; // Keeps the original transparency of the Post dialog for when we want to hide it.

- (BOOL)isContentValid {
// Do validation of contentText and/or NSExtensionContext attachments here
return YES;
}

- ( 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 ];
}
- ( 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 ];
}
}

- ( 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 ];

//Mahantesh -- Store image url to NSUserDefaults

NSUserDefaults *defaults=[[NSUserDefaults alloc] initWithSuiteName:@"group.com.schemename.nameofyourshareappgroup"];
[defaults setObject:filePath forKey:@"url"];
[defaults synchronize];

return filePath;
}

- ( 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 Image
if ( [ itemProvider hasItemConformingToTypeIdentifier: ( NSString * ) kUTTypeImage ] )
{
// Load it, so we can get the path to it
[ itemProvider loadItemForTypeIdentifier: ( NSString * ) kUTTypeImage
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 ];
}
} ];
}
}
}
- ( 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 ];
}

#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

#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
#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
#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
@end

  1. In the openURL method of your application delegate

            //Slartibartfast -- For the case where we are opening app from an extension
    NSString *STATIC_FILE_HANDLE = @"file://";
    //If app is opened from share extension, do the following
    /*
    1.) Get path of shared file from NSUserDefaults
    2.) Get data from file and store in some variable
    3.) Create a new accesible unique file path
    4.) Dump data created into this file.
    */

    NSUserDefaults *defaults=[[NSUserDefaults alloc] initWithSuiteName:YOURAPP_STATIC_APP_GROUP_NAME];
    NSString *path=nil;
    if(defaults)
    {
    [defaults synchronize];
    path = [defaults stringForKey:@"url"];
    }

    if(path.length != 0)
    {
    NSData *data;
    //Get file path from url shared
    NSString * newFilePathConverted = [STATIC_FILE_HANDLE stringByAppendingString:path];
    url = [ NSURL URLWithString: newFilePathConverted ];
    data = [NSData dataWithContentsOfURL:url];
    //Create a regular access path because this app cant preview a shared app group path
    NSString *regularAccessPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSString *uuid = [[NSUUID UUID] UUIDString];
    //Copy file to a jpg image(ignore extension, will convert from png)
    NSString *uniqueFilePath= [ NSString stringWithFormat: @"/image%@.jpg", uuid];
    regularAccessPath = [regularAccessPath stringByAppendingString:uniqueFilePath];
    NSString * newFilePathConverted1 = [STATIC_FILE_HANDLE stringByAppendingString:regularAccessPath];
    url = [ NSURL URLWithString: newFilePathConverted1 ];
    //Dump existing shared file path data into newly created file.
    [data writeToURL:url atomically:YES];
    //Reset NSUserDefaults to Nil once file is copied.
    [defaults setObject:nil forKey:@"url"];

    }
    //Do what you want
    }

Thanks to EasyNativeExtensions for pointers

MacOS Share Extension: can't get activation types working

Started working after a system reboot



Related Topics



Leave a reply



Submit