Danut Pralea's answer is excellent, but the code seems too long for those who are looking for an easy way to send attachments via email programmatically.
The gist
I cut off his answer in order to take out only the important bits, and also reorganized it like this:
MFMailComposeViewController *mailComposer = [[MFMailComposeViewController alloc] init]; mailComposer.mailComposeDelegate = self; mailComposer.subject = @"Sample subject"; mailComposer.toRecipients = @[@" arthur@example.com ", @" jeanne@example.com ", ...]; mailComposer.ccRecipients = @[@" nero@example.com ", @" mashu@example.com ", ...]; [mailComposer setMessageBody:@"Sample body" isHTML:NO]; NSData *fileData = [NSData dataWithContentsOfFile:filePath]; [mailComposer addAttachmentData:fileData mimeType:mimeType fileName:fileName]; [self presentViewController:mailComposer animated:YES completion:nil];
This is basically the essence of this, it is enough, as it is. If, for example, you put this code in the action of a button, it will present a screen for composing an e-mail with the corresponding fields pre-filled, as well as the file that you want to add to the letter.
additional literature
Framework
MFMailComposeViewController
is under the MessageUI
Framework, so to use it, import (if you have not done so already) the Framework, for example:
#import <MessageUI/MessageUI.h>
Checking Mail Capabilities
Now, when you run the source code and have not yet set up an email account on your device (not sure what this behavior is on the simulator), this code will crash your application. It appears that if the email account has not yet been configured, executing [[MFMailComposeViewController alloc] init]
will still cause mailComposer
be zero, which caused the failure . Since the answer in a related question reads:
You must verify that MFMailComposeViewController can send your mail immediately before sending
You can do this using the canSendMail method:
if (![MFMailComposeViewController canSendMail]) { [self openCannotSendMailDialog]; return; }
This can be done right before you make [[MFMailComposeViewController alloc] init]
so that you can immediately inform the user about it.
CannotSendMail Processing
If canSendMail
returns false according to Apple Dev Docs, this means that the device is not configured to send mail. This may mean that the user may not have set up a Mail account yet. To help the user with this, you can offer to open the Mail application and set up your account. You can do it like this:
NSURL *mailUrl = [NSURL URLWithString:@"message://"]; if ([[UIApplication sharedApplication] canOpenURL:mailUrl]) { [[UIApplication sharedApplication] openURL:mailUrl]; }
Then you can implement openCannotSendMailDialog
as follows:
- (void)openCannotSendMailDialog { UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Error" message:@"Cannot send mail." preferredStyle:UIAlertControllerStyleAlert]; NSURL *mailUrl = [NSURL URLWithString:@"message://"]; if ([[UIApplication sharedApplication] canOpenURL:mailUrl]) { [alert addAction: [UIAlertAction actionWithTitle:@"Open Mail" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [[UIApplication sharedApplication] openURL:mailUrl]; }]]; [alert addAction: [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { }]]; } else { [alert addAction: [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { }]]; } [self presentViewController:alert animated:YES completion:nil]; }
Mime Types
If you, like me, you forgot / don't know which mimeType
use, here is a resource that you can use. Most likely, text/plain
sufficient if the attached file is plain text or image/jpeg
/ image/png
for images.
delegate
As you probably noticed, Xcode gives us a warning in the following line:
mailComposer.mailComposeDelegate = self;
This is due to the fact that we have not yet established ourselves to comply with the relevant protocol and implement its delegation method. If you want to receive messages about whether the mail was canceled, saved, sent or even failed to send, you need to set your class in accordance with the MFMailComposeViewControllerDelegate
protocol, and process the following events :
- MFMailComposeResultSent
- MFMailComposeResultSaved
- MFMailComposeResultCancelled
- MFMailComposeResultFailed
According to Apple Dev Docs (emphasis mine):
The comment submission controller for mail is not automatically disabled . When the user selects the buttons to send an email message or cancel the interface, the mail message view controller calls the mailComposeController: didFinishWithResult: error: delegate method. Your implementation of this method should explicitly reject the view controller .
With this in mind, we can then implement the delegate method as follows:
- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error { switch (result) { case MFMailComposeResultSent: // Mail was sent break; case MFMailComposeResultSaved: // Mail was saved as draft break; case MFMailComposeResultCancelled: // Mail composition was cancelled break; case MFMailComposeResultFailed: // break; default: // break; } // Dismiss the mail compose view controller. [controller dismissViewControllerAnimated:YES completion:nil]; }
Conclusion
The final code might look like this:
- (void)openMailComposerWithSubject:(NSString *)subject toRecipientArray:(NSArray *)toRecipientArray ccRecipientArray:(NSArray *)ccRecipientArray messageBody:(NSString *)messageBody isMessageBodyHTML:(BOOL)isHTML attachingFileOnPath:(NSString)filePath mimeType:(NSString *)mimeType { if (![MFMailComposeViewController canSendMail]) { [self openCannotSendMailDialog]; return; } MFMailComposeViewController *mailComposer = [[MFMailComposeViewController alloc] init]; mailComposer.mailComposeDelegate = self; mailComposer.subject = subject; mailComposer.toRecipients = toRecipientArray; mailComposer.ccRecipients = ccRecipientArray; [mailComposer setMessageBody:messageBody isHTML:isHTML]; NSData *fileData = [NSData dataWithContentsOfFile:filePath]; NSString *fileName = filePath.lastPathComponent; [mailComposer addAttachmentData:fileData mimeType:mimeType fileName:fileName]; [self presentViewController:mailComposer animated:YES completion:nil]; } - (void)openCannotSendMailDialog { UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Error" message:@"Cannot send mail." preferredStyle:UIAlertControllerStyleAlert]; NSURL *mailUrl = [NSURL URLWithString:@"message://"]; if ([[UIApplication sharedApplication] canOpenURL:mailUrl]) { [alert addAction: [UIAlertAction actionWithTitle:@"Open Mail" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [[UIApplication sharedApplication] openURL:mailUrl]; }]]; [alert addAction: [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { }]]; } else { [alert addAction: [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { }]]; } [self presentViewController:alert animated:YES completion:nil]; } - (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error { NSString *message; switch (result) { case MFMailComposeResultSent: message = @"Mail was sent."; break; case MFMailComposeResultSaved: message = @"Mail was saved as draft."; break; case MFMailComposeResultCancelled: message = @"Mail composition was cancelled."; break; case MFMailComposeResultFailed: message = @"Mail sending failed."; break; default:
When the button is pressed, the action is as follows:
- (IBAction)mailButtonTapped:(id)sender { NSString *reportFilePath = ... [self openMailComposerWithSubject:@"Report Files" toRecipientArray:mainReportRecipientArray ccRecipientArray:subReportRecipientArray messageBody:@"I have attached report files in this email" isMessageBodyHTML:NO attachingFileOnPath:reportFilePath mimeType:@"text/plain"]; }
I went overboard a bit, but you can take and use the code that I posted here with salt. Of course, you need to adapt it to your requirements, but it is up to you. (I also modified this answer from my working source code, so there may be errors somewhere, please make a comment if you find one :))