How do I set recipients for UIActivityViewController in iOS 6?
Asked Answered
L

6

33

I'm using the new UIActivityViewController class in iOS6 to provide the user with various sharing options. You can pass an array of parameters to it such as text, links and images and it does the rest.

How do I define recipients? For example sharing via mail or SMS should be able to accept recipients but I can't figure out how to invoke this behaviour.

I don't want to have to have to use MFMessageComposeViewController and UIActivityViewController separately as that just defeats the purpose of the share controller.

Any suggestions?

UIActivityViewController Class Reference

Edit: This has now been submitted Apple and subsequently merged with a duplicate bug report.

Bug report on OpenRadar

Laze answered 27/9, 2012 at 14:4 Comment(0)
P
3

All credit here goes to Emanuelle, since he came up with most of the code.

Though I thought I would post a modified version of his code that helps set the to recipient.

I used a Category on MFMailComposeViewController

#import "MFMailComposeViewController+Recipient.h"
#import <objc/message.h>

@implementation MFMailComposeViewController (Recipient)

+ (void)load {
    MethodSwizzle(self, @selector(setMessageBody:isHTML:), @selector(setMessageBodySwizzled:isHTML:));
}



static void MethodSwizzle(Class c, SEL origSEL, SEL overrideSEL)
{
    Method origMethod = class_getInstanceMethod(c, origSEL);
    Method overrideMethod = class_getInstanceMethod(c, overrideSEL);

    if (class_addMethod(c, origSEL, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod))) {
        class_replaceMethod(c, overrideSEL, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
    } else {
        method_exchangeImplementations(origMethod, overrideMethod);
    }
}

- (void)setMessageBodySwizzled:(NSString*)body isHTML:(BOOL)isHTML
{
    if (isHTML == YES) {
        NSRange range = [body rangeOfString:@"<torecipients>.*</torecipients>" options:NSRegularExpressionSearch|NSCaseInsensitiveSearch];
        if (range.location != NSNotFound) {
            NSScanner *scanner = [NSScanner scannerWithString:body];
            [scanner setScanLocation:range.location+14];
            NSString *recipientsString = [NSString string];
            if ([scanner scanUpToString:@"</torecipients>" intoString:&recipientsString] == YES) {
                NSArray * recipients = [recipientsString componentsSeparatedByString:@";"];
                [self setToRecipients:recipients];
            }
            body = [body stringByReplacingCharactersInRange:range withString:@""];
        }
    }
    [self setMessageBodySwizzled:body isHTML:isHTML];
}

@end
Pricillaprick answered 21/3, 2014 at 11:11 Comment(2)
Can you share full example?Moussaka
Has this worked for anyone? If so can you share the full code?Particularly
T
24

For adding subject to the email using UIActivityViewController on iOS6, this is the best solution that anyone can use.. All you have to do is call the following while initializing UIActivityViewController.

UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:applicationActivities];
[activityViewController setValue:@"My Subject Text" forKey:@"subject"];

And your UIActivityViewController is populated with a subject.

Tavel answered 28/6, 2013 at 16:2 Comment(3)
Great solution using KVO!Shields
How about Recipient? Can we add like that?Kwashiorkor
this is adding subject in mail onlySwathe
P
10

I just come up with a solution to this problem (in my case set the subject of the email): as internally the UIActivityViewController will call at some point the setMessageBody:isHTML: method of the MFMailComposeViewController class, just intercept that call and inside make a call to the setSubject: method. Thanks to "method swizzling" technic, it looks like:

#import <objc/message.h>

static void MethodSwizzle(Class c, SEL origSEL, SEL overrideSEL)
{
    Method origMethod = class_getInstanceMethod(c, origSEL);
    Method overrideMethod = class_getInstanceMethod(c, overrideSEL);

    if (class_addMethod(c, origSEL, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod))) {
        class_replaceMethod(c, overrideSEL, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
    } else {
        method_exchangeImplementations(origMethod, overrideMethod);
    }
}

@implementation MFMailComposeViewController (force_subject)

- (void)setMessageBodySwizzled:(NSString*)body isHTML:(BOOL)isHTML
{
    if (isHTML == YES) {
        NSRange range = [body rangeOfString:@"<title>.*</title>" options:NSRegularExpressionSearch|NSCaseInsensitiveSearch];
        if (range.location != NSNotFound) {
            NSScanner *scanner = [NSScanner scannerWithString:body];
            [scanner setScanLocation:range.location+7];
            NSString *subject = [NSString string];
            if ([scanner scanUpToString:@"</title>" intoString:&subject] == YES) {
                [self setSubject:subject];
            }
        }
    }
    [self setMessageBodySwizzled:body isHTML:isHTML];
}

@end

Call the following line of code before using UIActivityViewController:

MethodSwizzle([MFMailComposeViewController class], @selector(setMessageBody:isHTML:), @selector(setMessageBodySwizzled:isHTML:));

Then pass to the UIActivityViewController a custom UIActivityItemProvider that for UIActivityTypeMail returns a HTML NSString like:

<html><head>
<title>Subject of the mail</title>
</head><body>
Body of the <b>mail</b>
</body></html>

The subject of the email is extracted from the HTML title (use plain text for that part, no html entities or tags).

Using that method, I let you elaborate an elegant way to set the recipient for the mail.

Preset answered 29/1, 2013 at 18:50 Comment(0)
C
8

While it does appear that at present the mailto: solution for setting email subject and body isn't working, this would in any case not be adequate if you wanted to set the email body to contain HTML and still make use of Apple's system email icon via UIActivityViewController.

That was exactly what we wanted to do: use the system icon, but have the email contain an HTML body and a custom subject.

Our solution was something of a hack, but it works well, at least for the moment. It does involve using MFMailComposeViewController, but it still lets you use the system mail icon with UIActivityViewController.

Step 1: Create a wrapper class conforming to the UIActivityItemSource like so:

    @interface ActivityItemSource : NSObject <UIActivityItemSource>
    @property (nonatomic, strong) id object;
    - (id) initWithObject:(id) objectToUse;
    @end

    @implementation ActivityItemSource

   - (id) initWithObject:(id) objectToUse
    {
        self = [super init];
        if (self) {
            self.object = objectToUse;
        }
        return self;
    }


    - (id)activityViewController:(UIActivityViewController *)activityViewController                 itemForActivityType:(NSString *)activityType
    {
    return self.object;
    }

    - (id)activityViewControllerPlaceholderItem:(UIActivityViewController *)activityViewController
    {

        return self.object;
    }

Step 2: Subclass UIActivityViewController and make it into a MFMailComposeViewControllerDelegate like so:

    @interface ActivityViewController : UIActivityViewController         <MFMailComposeViewControllerDelegate>

    @property (nonatomic, strong) id object;

    - (id) initWithObject:(id) objectToUse;
    @end


    @implementation ActivityViewController

    - (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error
    {

        switch (result)
        {
            case MFMailComposeResultSent:
            case MFMailComposeResultSaved:
                //successfully composed an email
                break;
            case MFMailComposeResultCancelled:
                break;
            case MFMailComposeResultFailed:
                break;
        }

    //dismiss the compose view and then the action view
        [self dismissViewControllerAnimated:YES completion:^() {
            [self.presentingViewController dismissViewControllerAnimated:YES completion:nil];        
        }];

    }

    - (id) initWithObject:(id) objectToUse
    {

        self = [super initWithActivityItems:[NSArray arrayWithObjects:[[ActivityItemSource alloc] initWithObject:objectToUse], nil] applicationActivities:nil];

        if (self) {
            self.excludedActivityTypes = [NSArray arrayWithObjects: UIActivityTypePostToWeibo, UIActivityTypePrint, UIActivityTypeCopyToPasteboard, UIActivityTypeAssignToContact, UIActivityTypeSaveToCameraRoll, nil];

            self.object = objectToUse;
        }
        return self;
    }

NOTE: when you are calling super initWithActivityItems you are wrapping the object you will be sharing in your custom ActivityItemSource

Step 3: Launch your own MFMailComposeViewController instead of the system one when a user taps on the Mail icon.

You would do this in the activityViewController:(UIActivityViewController *)activityViewController itemForActivityType:(NSString *)activityType method in the ActivityItemSource class:

    - (id)activityViewController:(UIActivityViewController *)activityViewController                 itemForActivityType:(NSString *)activityType
    {
        if([activityType isEqualToString:UIActivityTypeMail]) {
                //TODO: fix; this is a hack; but we have to wait till apple fixes the         inability to set subject and html body of email when using UIActivityViewController
                [self setEmailContent:activityViewController];
                return nil;
            }
         return self.object;
    }


    - (void) setEmailContent:(UIActivityViewController *)activityViewController 
    {

       MFMailComposeViewController *mailController = [ShareViewController mailComposeControllerWithObject: self.object withDelegate: activityViewController];

        [activityViewController presentViewController:mailController animated:YES completion:nil];

    }

In the mailComposeControllerWithObject method you instantiate an instance of the MFMailComposeViewController class and set it up to contain whatever data you want. Note also that you would set the activityViewController as the compose view's delegate.

The reason this works is that when a compose modal is displayed, it prevents other modals from being displayed, i.e. you displaying your own compose view blocks the system compose view from being shown. Definitely a hack, but it gets the job done.

Hope this helps.

Carnatic answered 17/1, 2013 at 16:41 Comment(10)
Can you tell me how MFMailComposeViewController *mailController = [ShareViewController mailComposeControllerWithObject: self.object withDelegate: activityViewController]; works? When I try MFMailComposeViewController *mailController = [[MFMailComposeViewController alloc] init]; mailController.delegate = activityViewController; I get Attempt to present <MFMailComposeViewController: 0x1edbc610> on <ActivityViewController: 0x20037560> which is waiting for a delayed presention of <MFMailComposeViewController: 0x200368b0> to complete errorJamisonjammal
Hi zeroos, we have abandoned this approach a little while ago, so it's possible that Apple has closed this loophole in the meantime. What iOS version are you building to? You might try building to the simulator with an earlier OS, e.g. 5.1, just to see if it's OS-related.Carnatic
If you have to, you may also try to display the mailComposeView from a different view controller. Presumably, that should still prevent the standard mailComposeView from appearing.Carnatic
I'm using iOS 6.1, in simulated iOS 6.0 i've got same problem. UIActiveViewController wasn't avaliable in iOS 5.1, so i'm looking for other solutions.Jamisonjammal
Right, UIActivityViewController came in with iOS6. In our case, we ended up creating a custom equivalent of the activity view controller, which gives us full control over how the icons appear and what content is shown in the social views we display.Carnatic
@Carnatic When doing this, the UIActivityViewController still shows the Mail sheet, but with nil as it's string. Am I missing something, or does this not work?Chatterjee
@lavoy, if you are successfully showing your own mail composer view, then you just need to set its body, subject, etc correctly.Carnatic
@Carnatic No, I mean that despite showing mine, the UIActivityViewController still shows its own.Chatterjee
@Carnatic How can I use this code in that controller which have share mail feature? kindly tell me.Hardball
@Carnatic itemForActivityType method is not called while click on mail icon from ActivityViewController. please commentHardball
P
3

All credit here goes to Emanuelle, since he came up with most of the code.

Though I thought I would post a modified version of his code that helps set the to recipient.

I used a Category on MFMailComposeViewController

#import "MFMailComposeViewController+Recipient.h"
#import <objc/message.h>

@implementation MFMailComposeViewController (Recipient)

+ (void)load {
    MethodSwizzle(self, @selector(setMessageBody:isHTML:), @selector(setMessageBodySwizzled:isHTML:));
}



static void MethodSwizzle(Class c, SEL origSEL, SEL overrideSEL)
{
    Method origMethod = class_getInstanceMethod(c, origSEL);
    Method overrideMethod = class_getInstanceMethod(c, overrideSEL);

    if (class_addMethod(c, origSEL, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod))) {
        class_replaceMethod(c, overrideSEL, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
    } else {
        method_exchangeImplementations(origMethod, overrideMethod);
    }
}

- (void)setMessageBodySwizzled:(NSString*)body isHTML:(BOOL)isHTML
{
    if (isHTML == YES) {
        NSRange range = [body rangeOfString:@"<torecipients>.*</torecipients>" options:NSRegularExpressionSearch|NSCaseInsensitiveSearch];
        if (range.location != NSNotFound) {
            NSScanner *scanner = [NSScanner scannerWithString:body];
            [scanner setScanLocation:range.location+14];
            NSString *recipientsString = [NSString string];
            if ([scanner scanUpToString:@"</torecipients>" intoString:&recipientsString] == YES) {
                NSArray * recipients = [recipientsString componentsSeparatedByString:@";"];
                [self setToRecipients:recipients];
            }
            body = [body stringByReplacingCharactersInRange:range withString:@""];
        }
    }
    [self setMessageBodySwizzled:body isHTML:isHTML];
}

@end
Pricillaprick answered 21/3, 2014 at 11:11 Comment(2)
Can you share full example?Moussaka
Has this worked for anyone? If so can you share the full code?Particularly
Z
1

You should be able to include the recipients using an NSUrl object with the mailto: scheme (or sms: for text messages).

From the UIActivity class reference:

UIActivityTypeMail The object posts the provided content to a new email message. When using this service, you can provide NSString and UIImage objects and NSURL objects pointing to local files as data for the activity items. You may also specify NSURL objects whose contents use the mailto scheme.

Therefore, something like this should work:

NSString *text = @"My mail text";
NSURL *recipients = [NSURL URLWithString:@"mailto:[email protected]"];

NSArray *activityItems = @[text, recipients];

UIActivityViewController *activityController =
                    [[UIActivityViewController alloc]
                    initWithActivityItems:activityItems
                    applicationActivities:nil];

[self presentViewController:activityController
                   animated:YES completion:nil];
Zygo answered 27/9, 2012 at 15:48 Comment(9)
Thanks for the suggestion. I've given it a try and the content of the mailto URL just gets added into the body of the email. So it looks like that isn't the solution despite the documentation.Laze
Hmm that is strange. Could you try removing the text from the activityItems array, and just passing the NSURL with the mailto?Zygo
You can extend the mailto to include the body, e.g. with mailto:[email protected][email protected]&subject=This%20is%20the%20subject&body=This%20is%20the%20bodyZygo
Just tried that too, wondered if you had to give the mailto before anything else but it still puts it into the body. Frustrating. Edit: I'll give that second bit a try.Laze
That is disappointing. Could you try the sms: scheme with the Messaging activity, just to check if that doesn't work as well. It seems like Apple forgot to implement it...Zygo
Nope. Passing that as both a string or a NSURL still prints the whole thing into the email body. Starting to think its not possible.Laze
I use HTML for my email content and in this case the NSURL is not printed in the body. But it also doesn't work, subject is still empty. I duped your rdar.Oeillade
You should be able to do that, but you can't. And in the iOS 7 documentation this claim about the mailto: URL is withdrawn. It never worked as advertised.Boswell
developer.apple.com/reference/uikit/… Link to the documentation: you'll see that any reference to using mailto: to set the recipients has been removedEvocation
L
1

I'm not sure about recipients, but it seems as though in iOS 7 and later you can set the subject of an email by conforming to the UIActivityItemSource protocol and implementing the method activityViewController:subjectForActivityType:.

Lofty answered 9/10, 2013 at 18:34 Comment(1)
Yes, this does work for the subject, no sweat. But there's still no non-swizzly way to set the recipient.Boswell

© 2022 - 2024 — McMap. All rights reserved.