Send an iphone attachment through email programmatically
Solution 1
try
[mailer addAttachmentData:myData mimeType:@"text/csv" fileName:@"expenses.csv"];
Edit: This is the code I'm using in my app:
- (IBAction) ExportData:(id)sender
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:kExportFileName];
self.timeRecords = [[NSMutableArray alloc] init];
for (int i=0; i< [self.selectedTimeEntries count]; i++)
for (int j=0; j<[[self.selectedTimeEntries objectAtIndex:i] count]; j++)
if ([[self.selectedTimeEntries objectAtIndex:i] objectAtIndex:j] == [NSNumber numberWithBool:YES])
[self.timeRecords addObject:[self timeEntriesForDay:[self.uniqueArray objectAtIndex:i] forIndex:j]];
if( !([self.timeRecords count]!=0))
{
UIAlertView *alert=[[UIAlertView alloc] initWithTitle:@"There are no time entries selected!" message:@"Please select at least one time entry before proceeding" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
[alert show];
[alert release];
return;
}
NSMutableString *csvLine;
NSError *err = nil;
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd"];
NSString *dateString = nil;
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setPositiveFormat:@"###0.##"];
NSString *formattedNumberString = nil;
if(![[NSFileManager defaultManager] fileExistsAtPath:filePath])
{
[[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
}
for (timeEntries *timeEntry in self.timeRecords) {
csvLine = [NSMutableString stringWithString:timeEntry.client];
[csvLine appendString:@","];
[csvLine appendString:timeEntry.category];
[csvLine appendString:@","];
[csvLine appendString:timeEntry.task];
[csvLine appendString:@","];
dateString = [dateFormatter stringFromDate:[NSDate dateWithTimeIntervalSince1970:timeEntry.date]];
[csvLine appendString:dateString];
[csvLine appendString:@","];
formattedNumberString = [numberFormatter stringFromNumber:timeEntry.duration];
[csvLine appendString:formattedNumberString];
[csvLine appendString:@","];
[csvLine appendString:timeEntry.description];
[csvLine appendString:@"\n"];
if([[NSFileManager defaultManager] fileExistsAtPath:filePath])
{
NSString *oldFile = [[NSString alloc] initWithContentsOfFile:filePath];
[csvLine insertString:oldFile atIndex:0];
BOOL success =[csvLine writeToFile:filePath atomically:NO encoding:NSUTF8StringEncoding error:&err];
if(success){
}
[oldFile release];
}
}
if (!appDelegate.shouldSendCSV) {
self.csvText = csvLine;
}
if([[NSFileManager defaultManager] fileExistsAtPath:filePath])
{
[self emailExport:filePath];
}
self.selectedTimeEntries =nil;
self.navigationController.toolbarHidden = NO;
}
- (void)emailExport:(NSString *)filePath
{
NSLog(@"Should send CSV = %@", [NSNumber numberWithBool:appDelegate.shouldSendCSV]);
MFMailComposeViewController *picker = [[MFMailComposeViewController alloc] init];
picker.mailComposeDelegate = self;
// Set the subject of email
[picker setSubject:@"My Billed Time Export"];
// Add email addresses
// Notice three sections: "to" "cc" and "bcc"
NSString *valueForEmail = [[NSUserDefaults standardUserDefaults] stringForKey:@"emailEntry"];
NSString *valueForCCEmail = [[NSUserDefaults standardUserDefaults] stringForKey:@"ccEmailEntry"];
if( valueForEmail == nil || [valueForEmail isEqualToString:@""])
{
UIAlertView *alert=[[UIAlertView alloc] initWithTitle:@"Please set an email address before sending a time entry!" message:@"You can change this address later from the settings menu of the application!" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
[alert show];
[alert release];
return;
}
else {
[picker setToRecipients:[NSArray arrayWithObjects:valueForEmail, nil]];
}
if(valueForCCEmail != nil || ![valueForCCEmail isEqualToString:@""])
{
[picker setCcRecipients:[NSArray arrayWithObjects:valueForCCEmail, nil]];
}
// Fill out the email body text
NSString *emailBody = @"My Billed Time Export File.";
// This is not an HTML formatted email
[picker setMessageBody:emailBody isHTML:NO];
if (appDelegate.shouldSendCSV) {
// Create NSData object from file
NSData *exportFileData = [NSData dataWithContentsOfFile:filePath];
// Attach image data to the email
[picker addAttachmentData:exportFileData mimeType:@"text/csv" fileName:@"MyFile.csv"];
} else {
[picker setMessageBody:self.csvText isHTML:NO];
}
// Show email view
[self presentModalViewController:picker animated:YES];
// Release picker
[picker release];
}
Solution 2
Danut Pralea's answer is great, however the code seems to be too long for someone looking for a simple way to send an attachment through email programatically.
The Gist
I pruned his answer to take out only the important bits, as well as refactored it like so:
MFMailComposeViewController *mailComposer = [[MFMailComposeViewController alloc] init];
mailComposer.mailComposeDelegate = self;
mailComposer.subject = @"Sample subject";
mailComposer.toRecipients = @[@"[email protected]", @"[email protected]", ...];
mailComposer.ccRecipients = @[@"[email protected]", @"[email protected]", ...];
[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];
That's basically the gist of it, this is enough as it is. If, for example, you put this code on a button's action, it will present an email composing screen with the respective fields pre-filled up, as well as having the file you want attached to the email.
Further Reading
Framework
The MFMailComposeViewController
is under the MessageUI
Framework, so to use it, import (if you have not done yet) the Framework like so:
#import <MessageUI/MessageUI.h>
Mail Capability Checking
Now when you run the source code, and you have not yet setup a mail account on your device, (not sure what the behavior is on simulator), this code will crash your app. It seems that if the mail account is not yet setup, doing [[MFMailComposeViewController alloc] init]
will still result in the mailComposer
being nil, causing the crash. As the answer in the linked question states:
You should check is MFMailComposeViewController are able to send your mail just before sending
You can do this by using the canSendMail method like so:
if (![MFMailComposeViewController canSendMail]) {
[self openCannotSendMailDialog];
return;
}
You can put this right before doing [[MFMailComposeViewController alloc] init]
so that you can notify the user immediately.
Handling cannotSendMail
If canSendMail
returns false, according to Apple Dev Docs, that means that the device is not configured for sending mail. This could mean that maybe the user has not yet setup their Mail account. To help the user with that, you can offer to open the Mail app and setup their account. You can do this like so:
NSURL *mailUrl = [NSURL URLWithString:@"message://"];
if ([[UIApplication sharedApplication] canOpenURL:mailUrl]) {
[[UIApplication sharedApplication] openURL:mailUrl];
}
You can then implement openCannotSendMailDialog
like so:
- (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 like me, you forgot/are unsure which mimeType
to use, here is a resource you can use. Most probably, text/plain
is enough, if the file you are attaching is just a plain text, or image/jpeg
/ image/png
for images.
Delegate
As you probably noticed, Xcode throws us a warning on the following line:
mailComposer.mailComposeDelegate = self;
This is because we have not yet set ourself to conform to the appropriate protocol and implement its delegate method. If you want to receive events whether the mail was cancelled, saved, sent or even failed sending, you need to set your class to conform to the protocol MFMailComposeViewControllerDelegate
, and handle the following events:
- MFMailComposeResultSent
- MFMailComposeResultSaved
- MFMailComposeResultCancelled
- MFMailComposeResultFailed
According to Apple Dev Docs (emphasis mine):
The mail compose view controller is not dismissed automatically. When the user taps the buttons to send the email or cancel the interface, the mail compose view controller calls the mailComposeController:didFinishWithResult:error: method of its delegate. Your implementation of that method must dismiss the view controller explicitly.
With this in mind, we can then implement the delegate method like so:
- (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 may look like so:
- (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:
//
break;
}
// Dismiss the mail compose view controller.
[controller dismissViewControllerAnimated:YES completion:^{
if (message) {
UIAlertController *alert =
[UIAlertController alertControllerWithTitle:@"Confirmation"
message:message
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:
[UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleCancel
handler:^(UIAlertAction * _Nonnull action) {
}]];
[self presentViewController:alert animated:YES completion:nil];
}
}];
}
With the button action looking like:
- (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 kind of went overboard here, but you can, with a grain of salt, take and use the code I posted here. Of course there is a need to adapt it to your requirements, but that is up to you. (I also modified this answer from my working source code, so there might be errors somewhere, please do comment if you find one :))
Comments
-
coder almost 2 years
I am writing an iPhone app that requires that I send an e-mail attachment programmatically. The attachment is a csv file, that I create through the code. I then attach the file to the email, and the attachment shows up on the phone. When I send the email to myself, however, the attachment doesn't appear in the e-mail. Here is the code I'm using.
[self exportData]; if ([MFMailComposeViewController canSendMail]) { NSString *filePath = [[NSBundle mainBundle] pathForResource:@"expenses" ofType:@"csv"]; NSData *myData = [NSData dataWithContentsOfFile:filePath]; MFMailComposeViewController *mailer = [[MFMailComposeViewController alloc] init]; mailer.mailComposeDelegate = self; [mailer setSubject:@"Vehicle Expenses from myConsultant"]; NSString *emailBody = @""; [mailer setMessageBody:emailBody isHTML:NO]; [mailer addAttachmentData:myData mimeType:@"text/plain" fileName:@"expenses"]; [self presentModalViewController:mailer animated:YES]; [mailer release]; } else { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Failure" message:@"Your device doesn't support the composer sheet" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alert show]; [alert release]; } } - (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error { switch (result) { case MFMailComposeResultCancelled: NSLog(@"Mail cancelled: you cancelled the operation and no email message was queued."); break; case MFMailComposeResultSaved: NSLog(@"Mail saved: you saved the email message in the drafts folder."); break; case MFMailComposeResultSent: NSLog(@"Mail send: the email message is queued in the outbox. It is ready to send."); break; case MFMailComposeResultFailed: NSLog(@"Mail failed: the email message was not saved or queued, possibly due to an error."); break; default: NSLog(@"Mail not sent."); break; } // Remove the mail view [self dismissModalViewControllerAnimated:YES];
The is being successfully created- I checked in the simulator files.
- (void) exportData { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES); NSString *documentsDir = [paths objectAtIndex:0]; NSString *root = [documentsDir stringByAppendingPathComponent:@"expenses.csv"]; NSString *temp=@"Date,Purpose,Start Odometer,End Odometer, Total Driven, Fees, "; for(int i = 0; i < expenses.count; i++){ VehicleExpense *tempExpense = [expenses objectAtIndex:i]; temp = [temp stringByAppendingString:tempExpense.date]; temp = [temp stringByAppendingString:@", "]; temp = [temp stringByAppendingString:tempExpense.purpose]; temp = [temp stringByAppendingString:@", "]; temp = [temp stringByAppendingString:[NSString stringWithFormat: @"%.02f",tempExpense.start_mile]]; temp = [temp stringByAppendingString:@", "]; temp = [temp stringByAppendingString:[NSString stringWithFormat: @"%.02f",tempExpense.end_mile]]; temp = [temp stringByAppendingString:@", "]; temp = [temp stringByAppendingString:[NSString stringWithFormat: @"%.02f",tempExpense.distance]]; temp = [temp stringByAppendingString:@", "]; temp = [temp stringByAppendingString:[NSString stringWithFormat: @"%.02f",tempExpense.fees]]; temp = [temp stringByAppendingString:@", "]; } [temp writeToFile:root atomically:YES encoding:NSUTF8StringEncoding error:NULL]; NSLog(@"got here in export data--- %@", documentsDir); }
-
Keale over 6 years@Shebuka In your proposed edit, if the mailComposer is the one to be dismissed, shouldn't it be
[controller dismissView...
instead of[self dismissView...
? -
Shebuka over 6 yearsyes, and I'm using
controller
, but apple official docs useself
... developer.apple.com/documentation/messageui/… Listing 3 -
Keale over 6 years@Shebuka Well, that's weird. I opened the link, and I certainly saw the sample code using
[self dismiss...
. The Swift version does usecontroller.dismiss...
though. -
Keale over 6 yearsLol, as of this writing, the ObjC link cannot be opened. It's probably undergoing correction? I'll check the link later to see if it has indeed changed. BTW, have you also tried using
[self dismiss..
? I am in a different project right now so I am unable to confirm it. -
Keale over 6 years...and the link's now up. The part that explains the usage of the delegate hasn't changed. I strongly believe that the correct one is
[controller dismiss...
though, so I will be updating the answer to reflect this. Thanks for the edit btw. I hope I was able to help you with this answer :) -
Shebuka over 6 yearsI've seen too that Swift version uses
controller...
so I'm using it too in ObjC as I think it's the correct approach.self
also works. Btw I think your answer must be the accepted answer to this question, it has really helped. -
Keale over 6 years@Shebuka I updated the answer to add more details regarding delegate implementation, and I used
[controller dismiss...
. Thanks :) Well, This question is really old, so the question asker will probably never accept my answer, but that's fine. I posted this answer knowing that it will not get accepted. I just wanted to prune the accepted answer because as you can see, it might be a bit too long for something as simple as sending an email with attachment programatically. But then, I got carried over and written a more detailed one. :P -
Keale over 6 yearsAnyways, the top part of my answer is really enough IMO. All the other details can be researched online. I just put them here so that it becomes easy to access.