Pregunta

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

¿Fue útil?

Solución

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

Otros consejos

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.

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.

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.

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:foo@bar.com"];

NSArray *activityItems = @[text, recipients];

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

[self presentViewController:activityController
                   animated:YES completion:nil];

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top