One thing you may notice is that with Mail, any text object that you pass to the standard Mail activity just get shown in the body of the email. How do you set the subject? Add an attachment? Set the recipient?
You can set all this using a custom UIActivity together with a UIActivityItemProvider object.
Step 1: Start with the Previous Example
This example builds upon the previous example about Customising Activity View Controller.
Step 2: Create another subclass of UIActivityItemProvider
As said in the previous post, the object of this class serves as a proxy for data object that is passed to the activity view controller through the activityItems array. In this example, we are creating a class called MailItemProvider, which will generate the components (i.e. email body, subject, etc) required to populate the email message. Because the UIActivityItemProvider object can only return a single object, all these components are put within a single object - a NSDictionary, which seems to be the most logical object to use for this purpose. (It appears, as of writing, that you cannot use NSArray as it is not one of the supported object type for this purpose.)
Insert the following codes in the viewController.m file (somewhere after the #import):
@interface MailItemProvider:UIActivityItemProvider
@property (nonatomic, strong) NSString *someText;
@end
@implementation MailItemProvider
@synthesize someText = _someText;
- (id)initWithPlaceholderItem:(id)placeholderItem
{
return [super initWithPlaceholderItem:placeholderItem];
}
- (id)item
{
return [NSDictionary dictionary];
}
-(id)activityViewController:(UIActivityViewController *)activityViewController itemForActivityType:(NSString *)activityType
{
NSDictionary *exportDetailsDict = [NSDictionary dictionary];
if ([activityType isEqualToString:kYourCustomMailType]) {
//-- create the attachment data
NSData *attachementData = [NSData data];
//-- message for the email
NSString *theMessage = [NSString stringWithFormat:@"Some message:\n%@",_someText];
//-- subject for the email
NSString *theSubject = @"Email Subject";
//-- create the dictionary
exportDetailsDict = [NSDictionary dictionaryWithObjectsAndKeys:attachementData,@"ATTACHMENT",
theMessage,@"MESSAGE",
theSubject,@"SUBJECT", nil];
}
return exportDetailsDict;
}
- (id)activityViewControllerPlaceholderItem:(UIActivityViewController *)activityViewController
{
return [NSDictionary dictionary];
}
@end
In addition to the above, you also need to define a NSString constant. Insert the following after the last #import line:
NSString *const kYourCustomMailType = @"YourCustomMailType";
The above codes follow the same logic as the previous post about using UIActivityItemProvider. Instead of returning a NSString object, the above return a NSDictionary.
This example only generates the body of the email message, the subject text and an attachment (which is just an empty pdf container here). You can of course populate the to, cc, and bcc recipient fields in your code as well.
Step 3: Create a custom UIActivity
Next, you need to roll your own UIActivity. We will create MailExportActivity which is a subclass of UIActivity. This class will handle your custom type (called 'YourCustomMailType' in this example) and present the standard mail interface using the MFMailComposerViewController. Unlike the standard Mail activity, our custom activity will allow you to pre-populate the various components of the email.
First, include the MessageUI framework, and #import <MessageUI/MessageUI.h>
Next, insert the following codes in the viewController.m file:
@interface MailExportActivity : UIActivity <MFMailComposeViewControllerDelegate>
@property (nonatomic, strong) NSArray *items;
@end
@implementation MailExportActivity
@synthesize items = _items;
- (NSString *)activityType
{
return kYourCustomMailType;
}
- (NSString *) activityTitle
{
return @"Custom Mail";
}
- (UIImage *) activityImage
{
//This creates an UIImage with the word 'mail' in the middle.
//Alternatively, you can use [UIImage imageNamed: ] to create the UIImage object
CGRect rect = CGRectMake(0.0f, 0.0f, 85.0f, 85.0f);
UIGraphicsBeginImageContext(rect.size);
rect = CGRectInset(rect, 15.0f, 15.0f);
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:10.0f];
[path stroke];
rect = CGRectInset(rect, 0.0f, 10.0f);
[@"Mail" drawInRect:rect withFont:[UIFont fontWithName:@"Futura" size:13.0f] lineBreakMode:NSLineBreakByWordWrapping alignment:NSTextAlignmentCenter];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (BOOL)canPerformWithActivityItems:(NSArray *)activityItems
{
//Determine whether this service should be displayed by the UIActivityController
//(This mostly depends if this service can do something with the objects/ data provided via the activityItems
for (id item in activityItems){
if ([item isKindOfClass:[NSDictionary class]]) {
return YES;
}
}
return NO;
}
- (void)prepareWithActivityItems:(NSArray *)activityItems
{
//This gets call when the user select this custom service
//Then, the controller will either call :
//(a) the activityViewController, or
//(b) performActivity.
self.items = activityItems;
}
- (UIViewController *) activityViewController
{
//-- codes to generate the export data
NSString *theSubject = @"Default Subject";
NSString *theMessage = @"Default Message";
NSString *theFileName = @"default.pdf";
NSData *theData;
for (id eachItem in _items){
if ([eachItem isKindOfClass:[NSDictionary class]]) {
NSDictionary *exportDetailsDict = eachItem;
theSubject = [exportDetailsDict objectForKey:@"SUBJECT"];
theMessage = [exportDetailsDict objectForKey:@"MESSAGE"];
theData = [exportDetailsDict objectForKey:@"ATTACHMENT"];
}
}
MFMailComposeViewController *mailComposeVC = [[MFMailComposeViewController alloc] init];
mailComposeVC.mailComposeDelegate = self;
[mailComposeVC setSubject:theSubject];
//[mailComposeVC setToRecipients:[[NSArray alloc] initWithObjects:@"[email protected]", nil]];
[mailComposeVC setMessageBody:theMessage isHTML:NO];
[mailComposeVC addAttachmentData:theData
mimeType:@"application/pdf"
fileName:theFileName];
return mailComposeVC;
}
#pragma martk - MFMailComposeViewControllerDelegate
- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error
{
if (result == MFMailComposeResultSent) {
//This notifies the system that your activity object has completed its work.
//This will dismiss the view controller provided via activityViewController method and the sharing interface provided by the UIActivityViewController
[self activityDidFinish:YES];
} else
{
[self activityDidFinish:NO];
}
}
@end
Comments have been inserted above, so it is self-explaining. It is simply overriding and implementing the methods in the UIActivity class. If anything is not clear, it is always good to consult the Apple Documentation.
When your custom activity icon is clicked, the activityItem created under step 2 above is passed to your custom class. The method (void)prepareWithActivityItems:(NSArray *)activityItems gets called. In this example, the activityItem object (which contains the NSDictionary) is assigned to the items NSArray.
When you roll your own UIActivity, you can present a UIViewController in which the user can provide additional input before perform whatever service you want to do on the data. The UIViewController that gets presented is the one returned under the (UIViewController *) activityViewController method. Inside this method, we have instantiated the usual MFMailComposeViewController and populated its properties with the values and data that we have passed in through the NSDictionary object inside the items NSArray.
Our custom MailExportActivity class also confirms to the MFMailComposeViewControllerDelegate protocol. (This what the bit of the code "UIActivity<MFMailComposeViewControllerDelegate>" means.) This is important as this provides the means to inform the Activity View Controller when our custom service has performed its task (i.e. sending the email) or when user otherwise cancel the task (such as clicking the 'cancel' button in the MFMailComposeView). When this happens, the delegate method (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error gets called. Inside this method we invoke the activityDidFinish:(BOOL)completed method of UIActivity class. This dismisses the view controller presented by the UIActivityViewController as well as the MFMailComposeView.
Step 4: Instantiate Your custom UIActivityItemProvider and the Your Custom UIActivity
Delete the activityItems NSArray and the instantiation of the UIActivityViewController under the previous post. Then insert the following:
MailItemProvider *mailItem = [[MailItemProvider alloc] initWithPlaceholderItem:[NSDictionary dictionary]];
mailItem.someText = _aTextView.text;
//--- initialise the custome activity
UIActivity *customMailActivity = [[MailExportActivity alloc] init];
NSArray *activityItems = [NSArray arrayWithObjects:textItem, mailItem, nil];
//-- initialising the activity view controller (with custom activity)
UIActivityViewController *avc = [[UIActivityViewController alloc]
initWithActivityItems:activityItems
applicationActivities:@[customMailActivity]];
[MFMailComposeViewController canSendMail]
If this returns true, you can initialise the above activity view controller; if false, you can initialise the plain vanilla activity view controller without the custom activity.