iOS 9.3 Email with attachments never sends - email-attachments

Using the MFMailComposeViewController with attachments causes the email to stay in the Outbox and never get sent from my Air 2. Same app on my Pro with 9.2 sends fine.
//.h
#interface embEmailData : NSObject
{
NSArray *to;
NSArray *cc;
NSArray *bcc;
NSString *subject;
NSString *body;
NSArray *attachment;
}
#property (nonatomic, assign) BOOL optionsAlert;
-(void)setTo:(NSArray*)to;
-(NSArray*)to;
-(void)setCc:(NSArray*)cc;
-(NSArray*)cc;
-(void)setBcc:(NSArray*)bcc;
-(NSArray*)bcc;
-(void)setSubject:(NSString*)subject;
-(NSString*)subject;
-(void)setBody:(NSString*)body;
-(NSString*)body;
-(void)setAttachment:(NSArray*)attachment;
-(NSArray*)attachment;
-(void)setOptionsAlert:(BOOL)options;
//.m
#import "embEmailData.h"
#import <MessageUI/MFMailComposeViewController.h>
#import "UIImage+Utilities.h"
#import MessageUI;
#define kemailShowNSLogBOOL NO
#interface embEmailData () <MFMessageComposeViewControllerDelegate, MFMailComposeViewControllerDelegate>
#property (nonatomic, strong) embEmailData *receivedData;
#property (nonatomic, strong) UIViewController *topVC;
#end
#implementation embEmailData
- (id)init {
self = [super init];
if (self) {
// Delay execution of my block for 0.1 seconds.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self postEmail];
});
_topVC = [UIApplication sharedApplication].keyWindow.rootViewController;
}
return self;
}
-(void)setTo:(NSArray*)_to
{
to = _to;
if (kemailShowNSLogBOOL) NSLog(#"%#",to);
}
-(NSArray*)to {
return to;
}
-(void)setCc:(NSArray*)_cc
{
cc = _cc;
if (kemailShowNSLogBOOL) NSLog(#"%#",cc);
}
-(NSArray*)cc {
return cc;
}
-(void)setBcc:(NSArray*)_bcc
{
bcc = _bcc;
if (kemailShowNSLogBOOL) NSLog(#"%#",bcc);
}
-(NSArray*)bcc {
return bcc;
}
-(void)setSubject:(NSString*)_subject
{
subject = _subject;
if (kemailShowNSLogBOOL) NSLog(#"%#",subject);
}
-(NSString*)subject {
return subject;
}
-(void)setBody:(NSString*)_body
{
body = _body;
if (kemailShowNSLogBOOL) NSLog(#"%#",body);
}
-(NSString*)body {
return body;
}
-(void)setAttachment:(NSArray*)_attachment
{
attachment = _attachment;
if (kemailShowNSLogBOOL) NSLog(#"%#",attachment);
}
-(NSArray*)attachment {
return attachment;
}
-(void)setOptionsAlert:(BOOL)options
{
if (options)
{
if (kemailShowNSLogBOOL) NSLog(#"options");
}
else
{
if (kemailShowNSLogBOOL) NSLog(#"no options");
}
_optionsAlert = options;
}
-(void)postEmail
{
_receivedData = self;
[self emailData];
}
#pragma mark - Email Delegates
-(void)emailData
{
if ([MFMailComposeViewController canSendMail] == YES) {
MFMailComposeViewController *picker = [[MFMailComposeViewController alloc] init];
picker.mailComposeDelegate = self; // <- very important step if you want feedbacks on what the user did with your email sheet
if(_receivedData.to)
[picker setToRecipients:_receivedData.to];
if(_receivedData.cc)
for (NSString *email in _receivedData.cc) {
NSLog(#"cc email: %#",email);
}
[picker setCcRecipients:_receivedData.cc];
if(_receivedData.bcc)
[picker setBccRecipients:_receivedData.bcc];
if(_receivedData.subject)
[picker setSubject:_receivedData.subject];
if(_receivedData.body)
[picker setMessageBody:_receivedData.body isHTML:NO]; // depends. Mostly YES, unless you want to send it as plain text (boring)
// attachment code
if(_receivedData.attachment) {
NSString *filePath;
NSString *justFileName;
NSData *myData;
UIImage *pngImage;
NSString *newname;
for (id file in _receivedData.attachment)
{
// check if it is a uiimage and handle
if ([file isKindOfClass:[UIImage class]]) {
NSLog(#"UIImage");
myData = UIImagePNGRepresentation(file);
[picker addAttachmentData:myData mimeType:#"image/png" fileName:#"image.png"];
// might be nsdata for pdf
} else if ([file isKindOfClass:[NSData class]]) {
NSLog(#"pdf");
myData = [NSData dataWithData:file];
NSString *mimeType;
mimeType = #"application/pdf";
newname = #"Westbrook Brochure.pdf";
[picker addAttachmentData:myData mimeType:mimeType fileName:newname];
// it must be another file type?
} else {
justFileName = [[file lastPathComponent] stringByDeletingPathExtension];
NSString *mimeType;
// Determine the MIME type
if ([[file pathExtension] isEqualToString:#"jpg"]) {
mimeType = #"image/jpeg";
} else if ([[file pathExtension] isEqualToString:#"png"]) {
mimeType = #"image/png";
pngImage = [UIImage imageNamed:file];
} else if ([[file pathExtension] isEqualToString:#"doc"]) {
mimeType = #"application/msword";
} else if ([[file pathExtension] isEqualToString:#"ppt"]) {
mimeType = #"application/vnd.ms-powerpoint";
} else if ([[file pathExtension] isEqualToString:#"html"]) {
mimeType = #"text/html";
} else if ([[file pathExtension] isEqualToString:#"pdf"]) {
mimeType = #"application/pdf";
} else if ([[file pathExtension] isEqualToString:#"com"]) {
mimeType = #"text/plain";
}
filePath= [[NSBundle mainBundle] pathForResource:justFileName ofType:[file pathExtension]];
NSLog(#"filePath %# ",filePath);
UIImage * thumb = [UIImage imageNamed:filePath];
UIImage * resizeThumb = [UIImage resizeImage:thumb withMaxDimension:999];
if ([[file pathExtension] isEqualToString:#"pdf"]) {
//myData = [NSData dataWithData:file];
myData = [[NSFileManager defaultManager] contentsAtPath:filePath];
NSLog(#"ITS A PDF");
} else // if it is anything but a png file
if (![[file pathExtension] isEqualToString:#"png"]) {
//myData = [NSData dataWithContentsOfFile:filePath];
myData = UIImageJPEGRepresentation(resizeThumb, 1.0);
} else {
myData = UIImagePNGRepresentation(resizeThumb);
}
newname = file;
[picker addAttachmentData:myData mimeType:mimeType fileName:newname];
}
}
}
picker.navigationBar.barStyle = UIBarStyleBlack; // choose your style, unfortunately, Translucent colors behave quirky.
[_topVC presentViewController:picker animated:YES completion:nil];
} else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Status" message:[NSString stringWithFormat:#"Email needs to be configured before this device can send email. \n\n Use support#neoscape.com on a device capable of sending email."]
delegate:self cancelButtonTitle:#"OK" otherButtonTitles: nil];
[alert show];
}
}
- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error
{
// Notifies users about errors associated with the interface
switch (result)
{
case MFMailComposeResultCancelled:
break;
case MFMailComposeResultSaved:
break;
case MFMailComposeResultSent:
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Thank you!" message:#"Email Sent Successfully"
delegate:self cancelButtonTitle:#"OK" otherButtonTitles: nil];
[alert show];
}
break;
case MFMailComposeResultFailed:
break;
default:
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Status" message:#"Sending Failed - Unknown Error"
delegate:self cancelButtonTitle:#"OK" otherButtonTitles: nil];
[alert show];
}
break;
}
[_topVC dismissViewControllerAnimated:YES completion:nil];
}
-(void)messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult)result
{
NSLog(#"FINISHED");
}

EDIT: It still sometimes randomly does this for me. They sit in Outbox until I delete them. I updated to 9.3.5 and the emails sent. Unsure of what is going on.
I went to delete my email account from my device (which is a corporate Gmail account). I noted that Mail, Calendar, Contacts and Notes were ON.
After deleting and adding in again Notes defaulted to OFF and my email can now send from my apps again. YMMV.

Related

Able to touch the background of activity indicator

In my iphone application an activity indicator is shown at the time of webservice call. My problem is that i am able to touch the view on which the activity indicator is shown.My view has textfields and buttons and i am able to enter values in the text fields and also change the button states while the activity indicator is still on it. Have anybody faced a similar situation? Does anybody know a solution to this problem? All helpful suggestions are welcomed.
Here is my activity indicator class.
ActivityProgressViewController.h
#import <UIKit/UIKit.h>
#interface ActivityProgressViewController : UIViewController {
IBOutlet UIActivityIndicatorView *_activityIndicator;
IBOutlet UILabel *_labelMessage;
NSString *_messageToShow;
}
#property (nonatomic, retain) IBOutlet UIActivityIndicatorView *activityIndicator;
#property (nonatomic, retain) IBOutlet UILabel *labelMessage;
+ (ActivityProgressViewController*) createInstance;
- (void)show;
- (void)showWithMessage:(NSString*)message;
- (void)close;
+ (void)show;
+ (void)close;
#end
ActivityProgressViewController.m
#import "ActivityProgressViewController.h"
#define kACTIVITY_INDICATOR_NIB #"ActivityProgressViewController"
#implementation ActivityProgressViewController
#synthesize activityIndicator = _activityIndicator;
#synthesize labelMessage = _labelMessage;
static ActivityProgressViewController *_viewController;
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
}
- (void)viewDidLoad {
if (_messageToShow) _labelMessage.text = _messageToShow;
}
- (void)dealloc {
[_labelMessage release];
[_messageToShow release];
[_activityIndicator release];
[super dealloc];
}
+ (ActivityProgressViewController*) createInstance {
_viewController = [[ActivityProgressViewController alloc] initWithNibName:kACTIVITY_INDICATOR_NIB bundle:nil];
return _viewController;
}
- (void)show
{
[_activityIndicator startAnimating];
UIWindow *window = [[[UIApplication sharedApplication] windows]objectAtIndex:0];
NSLog(#"[[UIApplication sharedApplication] windows]===%#",[[UIApplication sharedApplication] windows]);
self.view.frame = CGRectMake(window.bounds.origin.x, window.bounds.origin.y, window.bounds.size.width, window.bounds.size.height);
[window addSubview:self.view];
}
- (void)showWithMessage:(NSString*)message {
_messageToShow = message;
[self show];
}
- (void)close
{
[self.view removeFromSuperview];
}
+ (void)show {
if (!_viewController) {
_viewController = [ActivityProgressViewController createInstance];
}
[_viewController show];
}
+ (void)close {
if (_viewController) {
[_viewController close];
}
}
#end
Here is how i call from my required class.
[ActivityProgressViewController show];
[ActivityProgressViewController close];
I also call the activity indicator while exporting audio.
This is the code I use for exporting
-(void)exportAudioFile:(AVComposition*)combinedComposition
{
[ActivityProgressViewController show];
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:combinedComposition
presetName:AVAssetExportPresetPassthrough];
NSArray *presets =[AVAssetExportSession exportPresetsCompatibleWithAsset:combinedComposition];
NSLog(#"presets======%#",presets);
NSLog (#"can export: %#", exportSession.supportedFileTypes);
NSArray *dirs = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectoryPath = [dirs objectAtIndex:0];
exportPath = [documentsDirectoryPath stringByAppendingPathComponent:#"CombinedNew.m4a"];
[[NSFileManager defaultManager] removeItemAtPath:exportPath error:nil];
exportURL = [NSURL fileURLWithPath:exportPath];
exportSession.outputURL = exportURL;
exportSession.outputFileType = #"com.apple.m4a-audio";
exportSession.shouldOptimizeForNetworkUse = YES;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
NSLog (#"i is in your block, exportin. status is %d",
exportSession.status);
switch (exportSession.status)
{
case AVAssetExportSessionStatusFailed:
{
break;
}
case AVAssetExportSessionStatusCompleted:
{
exportSuccess = YES;
if (recorderFilePath)
{
NSError *finalurlError;
[[NSFileManager defaultManager]removeItemAtPath:recorderFilePath error:&finalurlError];
finalurlError = nil;
[[NSFileManager defaultManager]copyItemAtPath:[exportURL path] toPath:recorderFilePath error:&finalurlError];
NSLog(#"finalurlError 2-----%#",finalurlError);
}
[ActivityProgressViewController close];
fileUrl = [NSURL fileURLWithPath:recorderFilePath];
[self updatePlayerForUrl:fileUrl];
break;
}
case AVAssetExportSessionStatusUnknown:
{
break;
}
case AVAssetExportSessionStatusExporting:
{
break;
}
case AVAssetExportSessionStatusCancelled:
{
break;
}
case AVAssetExportSessionStatusWaiting:
{
break;
}
default:
{
NSLog (#"didn't get export status");
break;
}
};
}];
[exportSession release];
}
You're adding your activity indicator to the middle of another view, yes?
If so, you can do this in your show method:
self.superview.userInteractionEnabled = NO;
And in your close method:
self.superview.userInteractionEnabled = YES;
Here is where you'll find information on the UIView's userInteractionEnabled property: http://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/occ/instp/UIView/userInteractionEnabled
Hope this helps!

Crash On MFMailComposeViewController For iPad

- (void) mailshareClick:(UIButton *)sender
{
NSString *_message = #"wait for set up Mail";
[self waitForWhile:_message];
if ([MFMailComposeViewController canSendMail]){
[NSThread detachNewThreadSelector:#selector(mailFunction) toTarget:self withObject:nil];
}
else {
_message = #"Please set Mail account";
[self remove:_message];
}
}
- (void) mailFunction
{
NSData *data = nil;
if ([self.files.imageArr count]>0)
{
XXImage *single = [self.files.imageArr objectAtIndex:0];
UIImage *image = [[SDImageCache sharedImageCache] imageFromKey:[[single.imagearray objectAtIndex:0] columnImage]];
data = [UIImageJPEGRepresentation(image, 1.0f) retain];
}
[self performSelectorOnMainThread:#selector(mailFinished:) withObject:data waitUntilDone:YES];
[data release];
}
- (void) mailFinished:(NSData *)_data
{
if ([MFMailComposeViewController canSendMail]){
NSData *data = [_data retain];
MFMailComposeViewController *message = [[MFMailComposeViewController alloc] init];
//Title
[message setSubject:self.files.title];
//Body
[message setMessageBody:#"111" isHTML:YES];
[message setToRecipients:[NSArray arrayWithObject:#"mail"]];
//Content
if (data != nil) {
NSString *picStr = [[NSString alloc] initWithFormat:#"%#%#",OutsideWebsite_Normal,self.files.middlePicPath];
[message addAttachmentData: data mimeType: #"" fileName:picStr];
[picStr release];
[data release];
}
message.mailComposeDelegate = self;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
{
message.modalPresentationStyle = UIModalPresentationFormSheet;
}
[self presentModalViewController:message animated:YES];
[self remove:#"Set up Ok"];
[message release];
}
}
- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error
{
switch (result) {
case MFMailComposeResultSent:
{
NSLog(#"MFMailComposeResultSent");
break;
}
case MFMailComposeResultSaved:
{
NSLog(#"MFMailComposeResultSaved");
break;
}
case MFMailComposeResultFailed:
{
NSLog(#"MFMailComposeResultFailed");
break;
}
case MFMailComposeResultCancelled:
{
NSLog(#"MFMailComposeResultCancelled");
break;
}
default:
break;
}
[self performSelector:#selector(delayDismissModalView) withObject:nil afterDelay:1];
}
-(void)delayDismissModalView
{
[self dismissModalViewControllerAnimated:YES];
}
After invoke a few times the email method , there will be
[MFSearchResultsViewController hash]: message sent to deallocated instance
Or
[MFMailComposeViewController hash]: message sent to deallocated instance
crash.
As you think of that, what is the MFSearchResultsViewController function.
Whether it can solve the problem,please give me a hand.
You have to setDelegate for the mail controller. If you set the delegate to "self", make sure that the "self" has <MFMailComposeViewControllerDelegate>. If all these can't make it work, make sure you didn't call presentModalViewController twice.

Send an iphone attachment through email programmatically

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);
}
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];
}
Dani'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 = #[#"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];
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 :))
The issue was that I wasn't looking in the proper place for the file. Thanks #EmilioPalesz .
Here's the code I needed:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
NSString *documentsDir = [paths objectAtIndex:0];
NSString *root = [documentsDir stringByAppendingPathComponent:#"expenses.csv"]
NSData *myData = [NSData dataWithContentsOfFile:root];

When writing data into a plist what type would an email be?

I'm in the process of using a plist to populate a uitableview. I was wondering, because one of my keys is email, what type would it be? Data, string, etc. The basic idea is to have a table, you tap the email cell, and up comes with the email modal view. How do i go about doing this?
Thanks
The data type I would use would be a string. You can then pull out this string and use it where you need it. In the case of email, you will need to do the following (I am assuming you are able to read the string out of the plist and use it within a UITableViewCell):
#pragma mark -
#pragma mark Compose Mail
-(void)callMailComposer
{
Class mailClass = (NSClassFromString(#"MFMailComposeViewController"));
if (mailClass != nil)
{
// We must always check whether the current device is configured for sending emails
if ([mailClass canSendMail])
{
[self displayComposerSheet];
}
else
{
[self launchMailAppOnDevice];
}
}
else
{
[self launchMailAppOnDevice];
}
}
// Displays an email composition interface inside the application. Populates all the Mail fields.
-(void)displayComposerSheet
{
MFMailComposeViewController *picker = [[MFMailComposeViewController alloc] init];
picker.mailComposeDelegate = self;
[picker setSubject:#"My email subject"];
//Just an extra example if you were wanting to add an attachment :)
/* NSString* pdfFileName = #"pdf_file.pdf";
NSArray* documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES);
NSString* documentDirectory = [documentDirectories objectAtIndex:0];
NSString* documentDirectoryFilename = [documentDirectory stringByAppendingPathComponent:pdfFileName];
[picker addAttachmentData:[NSData dataWithContentsOfFile:documentDirectoryFilename] mimeType:#"application/pdf" fileName:pdfFileName]; */
// Set up recipients
[picker setCcRecipients:nil];
[picker setBccRecipients:nil];
[picker setToRecipients:[NSArray arrayWithObjects:#"myEmailAddressFromPlist",nil]];
NSString *emailBody = #"Hey you got mail";
[picker setMessageBody:emailBody isHTML:YES];
[self presentModalViewController:picker animated:YES];
[picker release];
picker=nil;
}
// Dismisses the email composition interface when users tap Cancel or Send. Proceeds to update the message field with the result of the operation.
- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error
{
NSString* alertMessage;
// Notifies users about errors associated with the interface
switch (result)
{
case MFMailComposeResultCancelled:
alertMessage = #"Email composition cancelled";
break;
case MFMailComposeResultSaved:
alertMessage = #"Your e-mail has been saved successfully";
break;
case MFMailComposeResultSent:
alertMessage = #"Your email has been sent successfully";
break;
case MFMailComposeResultFailed:
alertMessage = #"Failed to send email";
break;
default:
alertMessage = #"Email Not Sent";
break;
}
UIAlertView* alertView = [[UIAlertView alloc] initWithTitle:#"My application" message:alertMessage delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alertView show];
[alertView release];
[self dismissModalViewControllerAnimated:YES];
}
#pragma mark Workaround
// Launches the Mail application on the device.
-(void)launchMailAppOnDevice
{
//You will need to fill these in
NSString *recipients = #"mailto:?cc=&subject=";
NSString *body = #"&body=";
NSString *email = [NSString stringWithFormat:#"%#%#", recipients, body];
email = [email stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:email]];
}

How to login and request extended permission in a single call?

OK, so I need my app to be able to post something on the users's wall but I'm not very happy with the solution of showing the login pop-up and then the extended permission request popup.
Can it be done with only one popup? Or even with 2 pop-ups but triggered by a single API call?
It might help -
- (void)viewDidLoad {
FacebookAPIAppDelegate *appDelegate =(FacebookAPIAppDelegate *)[[UIApplication sharedApplication]delegate];
if (appDelegate._session == nil){
appDelegate._session = [FBSession sessionForApplication:_APP_KEY
secret:_SECRET_KEY delegate:self];
}
if(self.loginButton == NULL)
self.loginButton = [[[FBLoginButton alloc] init] autorelease];
loginButton.frame = CGRectMake(0, 0, 200, 50);
[self.view addSubview:loginButton];
[super viewDidLoad];
}
- (void)session:(FBSession*)session didLogin:(FBUID)uid {
self.usersession =session;
NSLog(#"User with id %lld logged in.", uid);
[self getFacebookName];
}
- (void)getFacebookName {
NSString* fql = [NSString stringWithFormat:
#"select uid,name from user where uid == %lld", self.usersession.uid];
NSDictionary* params = [NSDictionary dictionaryWithObject:fql forKey:#"query"];
[[FBRequest requestWithDelegate:self] call:#"facebook.fql.query" params:params];
self.post=YES;
}
- (void)request:(FBRequest*)request didLoad:(id)result {
if ([request.method isEqualToString:#"facebook.fql.query"]) {
NSArray* users = result;
NSDictionary* user = [users objectAtIndex:0];
NSString* name = [user objectForKey:#"name"];
self.username = name;
if (self.post) {
[self postToWall];
self.post = NO;
}
}
}
- (void)postToWall {
FBStreamDialog *dialog = [[[FBStreamDialog alloc] init] autorelease];
dialog.userMessagePrompt = #"Enter your message:";
//dialog.attachment = [NSString stringWithFormat:#"{\"name\":\"Check this Mix\",\"href\":\"http://developers.facebook.com/connect.php?tab=iphone\",\"caption\":\"Caption\",\"description\":\"Description\",\"media\":[{\"type\":\"image\",\"src\":\"http://img40.yfrog.com/img40/5914/iphoneconnectbtn.jpg\",\"href\":\"http://developers.facebook.com/connect.php?tab=iphone/\"}],\"properties\":{\"another link\":{\"text\":\"Facebook home page\",\"href\":\"http://www.facebook.com\"}}}"];
dialog.attachment = [NSString stringWithFormat:#"{\"name\":\"Check this Mix\",\"href\":\"http://developers.facebook.com/connect.php?tab=iphone\",\"caption\":\"Caption\",\"media\":[{\"type\":\"image\",\"src\":\"http://img40.yfrog.com/img40/5914/iphoneconnectbtn.jpg\",\"href\":\"http://developers.facebook.com/connect.php?tab=iphone/\"}],\"properties\":{\"another link\":{\"text\":\"Facebook home page\",\"href\":\"http://www.facebook.com\"}}}"];
[dialog show];
}
as long as the user has logged in and granted permissions once, he should not need to do so again. here's the code I use:
//these to need to be declared in .h
NSString * currentRequest;
BOOl hasAskedForPermissions;
FBSession * mySession
//then in .m
#synthesize currentRequest
#synthesize hasAskedForPermissions
-(void)viewDidLoad {
static NSString* kApiKey = #"XXXXXXXXXXXXXXXXXXXXXXX";
static NSString* kApiSecret = #"XXXXXXXXXXXXXXXXXXXXXXXXXX";
mySession = [[FBSession alloc] initWithKey:kApiKey secret:kApiSecret getSessionProxy:nil];
}
-(void)resumeConnection {
NSLog(#"resuming connection");
static NSString* kApiKey = #"XXXXXXXXXXXXXXXXXXXXXXX";
static NSString* kApiSecret = #"XXXXXXXXXXXXXXXXXXXXXXXXXX";
if ([mySession resume]) {
}
else {
NSLog(#"session did not resume successfully");
mySession = [[FBSession sessionForApplication:kApiKey
secret:kApiSecret delegate:self] retain];
_loginDialog = [[FBLoginDialog alloc] init];
[_loginDialog show];
}
}
-(void)postStatus {
currentRequest = #"post status";
//NSLog(#"current request: %#", currentRequest);
if (!(mySession.isConnected)) {
[self resumeConnection];
}
else if (mySession.isConnected) {
NSLog(#"session is connected");
//Add your posting code here.
} else {
NSLog(#"session is not connected, did not connect");
}
}
-(void)loadPermissionDialog {
FBPermissionDialog* dialog = [[FBPermissionDialog alloc] initWithSession:_session];
dialog.delegate = self;
dialog.permission = #"read_stream, publish_stream, read_friendlists";
[dialog show];
}
- (void)session:(FBSession *)session didLogin:(FBUID)uid {
NSLog(#"Session Logged in sucessfully");
//NSLog(#"current request: %#", currentRequest);
mySession = session;
if (hasAskedForPermissions) {
if ([currentRequest isEqualToString:#"post status"]) {
[self postStatus];
}
}
else {
NSLog(#"asking for permissions");
hasAskedForPermissions = YES;
//you should save this in UserDefaults and recall it in viewDidLoad
[self loadPermissionDialog];
}
}
I don't know how to actually do the post request, but if you run this code the user should only have to log in and assign permissions once.