以编程方式通过电子邮件发送iPhone附件

问题描述:

我正在编写一个iPhone应用程序,要求我以编程方式发送电子邮件附件。附件是一个csv文件,我通过代码创建。然后我将该文件附加到电子邮件中,并且附件显示在手机上。但是,当我将电子邮件发送给自己时,附件不会显示在电子邮件中。这是我正在使用的代码。以编程方式通过电子邮件发送iPhone附件

[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]; 

正在成功创建 - 我检查了模拟器文件。

- (void) exportData 
{ 
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES); 
    NSString *documentsDir = [paths objectAtIndex:0]; 
    NSString *root = [documentsDir stringByAppendingPathComponent:@"expenses.csv"]; 
    NSString *[email protected]"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); 

} 
+1

尝试记录myData,看它是否不返回零。 – EmilioPelaez

+0

它是空的......我如何获得文件?我正在编辑我的原始帖子,以显示我创建文件的位置。 – coder

+1

看起来像'filePath = [[NSBundle mainBundle] pathForResource:@“expenses”ofType:@“csv”];'没有得到你正在寻找的文件的路径。如果我是正确的,该方法只是获取.app文件夹中的资源。我建议您按照您在exportData方法中所做的方式来获取路径。 – EmilioPelaez

尝试 [mailer addAttachmentData:myData mimeType:@"text/csv" fileName:@"expenses.csv"];

编辑: 这是我用我的应用程序的代码:

- (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]; 
} 
+0

我刚刚尝试过,并没有帮助。 – coder

的问题是,我没有在正确的期待放置文件。谢谢@EmilioPalesz。

这是我所需要的代码:

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES); 
NSString *documentsDir = [paths objectAtIndex:0]; 
NSString *root = [documentsDir stringByAppendingPathComponent:@"expenses.csv"] 

NSData *myData = [NSData dataWithContentsOfFile:root]; 

Danut Praleaanswer是伟大的,但是代码似乎过长有人找简单的方法通过电子邮件发送附件编程

要义

我修剪他的回答仅取出了重要的位,以及重构,它像这样:

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]; 

这基本上是它的要点,这是不够的它是。例如,如果您将此代码放在按钮的操作上,它会显示一个电子邮件组成屏幕,其中包含预先填写的各个字段,以及将所需文件附加到电子邮件中。

进一步阅读

框架

MFMailComposeViewControllerMessageUI框架下,所以使用它,进口(如果您还没有完成)的框架,像这样:

#import <MessageUI/MessageUI.h> 

邮件能力检查

现在,当你运行源代码,并且你还没有在你的设备上设置一个邮件账户,(不知道模拟器上的行为是什么),这段代码将会崩溃你的应用程序。看来如果邮件账户还没有设置,做[[MFMailComposeViewController alloc] init]仍然会导致mailComposer为零,causing the crash。作为链接的问题answer指出:

您应该检查是MFMailComposeViewController能够发送邮件发送

之前您可以通过使用canSendMail方法,像这样做:

if (![MFMailComposeViewController canSendMail]) { 
    [self openCannotSendMailDialog]; 
    return; 
} 

您可以在做[[MFMailComposeViewController alloc] init]之前将此权利,以便您可以立即通知用户。

处理cannotSendMail

如果canSendMail返回false,根据苹果开发文档,这意味着该设备未配置为发送邮件。这could mean可能是用户尚未设置其邮件帐户。为了帮助用户,你可以提供open the Mail app并设置他们的帐户。你可以这样做,像这样:

NSURL *mailUrl = [NSURL URLWithString:@"message://"]; 
if ([[UIApplication sharedApplication] canOpenURL:mailUrl]) { 
    [[UIApplication sharedApplication] openURL:mailUrl]; 
} 

然后,您可以实现openCannotSendMailDialog像这样:

- (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类型

如果像我一样,你忘了/不能确定使用哪个mimeTypehere是您可以使用的资源。如果您附加的文件只是纯文本或图像的image/jpeg/image/png,最有可能的就是text/plain就足够了。

代表

正如你可能已经注意到,Xcode中抛出我们下面的行警告:

mailComposer.mailComposeDelegate = self; 

这是因为我们还没有设置我们自己以符合相应的协议和实施它的代表方法。如果你想接收事件的邮件是否被取消,保存,发送,甚至发送失败,你需要设置你的类符合协议MFMailComposeViewControllerDelegate,并处理following events

  • MFMailComposeResultSent
  • MFMailComposeResultSaved
  • MFMailComposeResultCancelled
  • MFMailComposeResultFailed

据苹果开发文档(重点矿山):

邮件撰写视图控制器不会自动解除。当用户点击按钮发送电子邮件或取消界面时,邮件撰写视图控制器会调用其委托的mailComposeController:didFinishWithResult:error:方法。 您的该方法的实现必须明确地关闭视图控制器

考虑到这一点,我们就可以实现委托方法像这样:

- (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]; 
} 

结论

最后的代码可能看起来像这样:

- (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]; 
     } 
    }]; 
} 

按钮动作如下所示:

- (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"];  
} 

我有点过分在这里,但你可以用一粒盐,拿走并使用我在这里发布的代码。当然,有必要根据您的要求进行调整,但这取决于您。 (我也从我的工作源代码修改了这个答案,所以可能有错误的地方,请做评论,如果你找到一个:))

+0

@Shebuka在你提出的编辑中,如果mailComposer是被解雇的人,不应该是'[controller dismissView ...'而不是'[self dismissView ...'? – Keale

+1

是的,我正在使用'controller',但苹果官方文档使用'self' ... https://developer.apple.com/documentation/messageui/mfmailcomposeviewcontroller?language=objc清单3 – Shebuka

+0

@Shebuka那么,这是奇怪的。我打开链接,我当然看到了使用'[self dismiss ...'的示例代码。尽管[Swift](https://developer.apple.com/documentation/messageui/mfmailcomposeviewcontroller)版本确实使用'controller.dismiss ...'。 – Keale