Link to home
Start Free TrialLog in
Avatar of efz
efzFlag for United States of America

asked on

FireMonkey - iOS Email Attachments

Hello Esteemed Experts,

Two-Sentence Introduction

In May of 2012, a member of the Delphi Developer Network posted a FireMonkey iOS component/function for creating email attachments programmatically. Although not all programs produce non-screen output (i.e., printouts, files, etc.), many do and in the context of iOS program development, that makes the creation of email attachments (as in the case of the iPad word processor Pages) vitally important. Consequently, I was thrilled to find this iOS Email Attachment component/function.

The Code

Here's the code. If you take out the comments, it's barely 100 lines long.

unit iosEmail;

(* ----------------------------------------------------------------------------
   email component and function for IOS
   author: Xavier Dufaure de Citres, 2012

   usage: 1. add the component (TiOSEmail) to your form and use iOSEmail1.sendEmail; 
             to trigger the display of the email on the device
          2. call the  iosSendEmail directly (see below)  
   
   note:  you can only have one instance of the componnent, if you have a component 
          create you cannot use the direct call function.

   version 0.92 June 15th 2012

   TODO
       - handle to,CC and BCC  currently only support one email ( TiOSEmail.SendEmail )
       - detect mime type      ( TiOSEmail.SendEmail.addAttachement )
       - Better way to change name/extension of attached file.

       - I exposed "mainWindow" by modifying the FMX_Platform_iOS.pas (see https://forums.embarcadero.com/message.jspa?messageID=402437 for possible workaround)
  ----------------------------------------------------------------------------
*)
{$IFDEF FPC}
{$mode delphi}
{$modeswitch objectivec2}
{$ENDIF}

interface

uses SysUtils, Classes, FMX_Forms,FMX_Types{$IFDEF FPC}, iPhoneAll,FMX_Platform_iOS {$ENDIF};
  
type TEmailresult = ( erSent,              // email was sent succesfully
                      erCannotSendEmail,   // email is not configured on the device
                      erCancelled,         // user canceled the email without saving it
                      erSaved,             // user canceled the email but saved it int he draft
                      erFailed,            // email failed to be sent
                      erUknown);           // unknown error, assume failure
  
type
  MFMailComposeResult = integer;
const
  MFMailComposeResultCancelled = 0;
  MFMailComposeResultSaved = 1;
  MFMailComposeResultSent = 2;
  MFMailComposeResultFailed = 3;  


{$IFDEF FPC}

{$linkframework MessageUI}

type
  MFMailComposeViewController = objcclass external (UINavigationController)
  private
    _internal: id;
  public
    class function canSendMail: Boolean; message 'canSendMail';
    procedure setMailComposeDelegate (newValue: id); message 'setMailComposeDelegate:';
    function mailComposeDelegate: id; message 'mailComposeDelegate';
    procedure setSubject(subject: NSString ); message 'setSubject:';
    procedure setToRecipients(toRecipients: NSArray); message 'setToRecipients:';
    procedure setCcRecipients(ccRecipients: NSArray); message 'setCcRecipients:';
    procedure setBccRecipients(bccRecipients: NSArray); message 'setBccRecipients:';
    procedure setMessageBody_isHTML(body: NSString; isHTML: Boolean); message 'setMessageBody:isHTML:';
    procedure addAttachmentData_mimeType_fileName(attachment: NSData; mimeType: NSString; filename: NSString); message 'addAttachmentData:mimeType:fileName:';
  end;

Type
  TMailDelegate = objcclass(NSObject)
    Emailcontroler  : MFMailComposeViewController;
    Done : Boolean;
    EmailResult : MFMailComposeResult;
    procedure mailComposeController_didFinishWithResult_error(
      controller: MFMailComposeViewController;
      result : MFMailComposeResult;
      error: NSErrorPtr); message 'mailComposeController:didFinishWithResult:error:';
  end;
{$ENDIF}

type
  TiOSEmail = class(TFmxObject)
  private
    { Private declarations }
    FSubject       : String;
    FRecipientsTo  : String;
    FRecipientsCC  : String;
    FRecipientsBCC : String;
    FBody          : String;
    FisHTML        : boolean;
    FFileAttachment: TstringList;
  protected
    { Protected declarations }
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    { Published declarations }
    property Subject: String read FSubject write FSubject;
    property RecipientsTo : String read FRecipientsTo write FRecipientsTo;
    property RecipientsCC : String read FRecipientsCC write FRecipientsCC;
    property RecipientsBCC: String read FRecipientsBCC write FRecipientsBCC;
    property Body: String read FBody write FBody;
    property isHTML: boolean  read FisHTML write FisHTML default True;
    property FileAttachment: TstringList read FFileAttachment write FFileAttachment;
    Function SendEmail : TEmailresult;
  end;

procedure Register;

function iosSendEmail( recTo,recCC,recBCC,subject,body : string; //comma delimited
                        FileAttachment : TstringList = nil;
                        isHTML: boolean = true ): TEmailresult;
{
examples
  iosSendEmail('Xavier@domain.com','','','my subject','<h1>Hi</h1> how are you?') 
  iosSendEmail('Xavier@domain.com, michelle@domain.com','','','my subject','<h1>Hi</h1> how are you?') 
}

implementation




{$IFDEF FPC}
//Procedure Log(S:string);
//Begin
//  NSLog( NSSTR(Pchar(S)) );
//end;


procedure TMailDelegate.mailComposeController_didFinishWithResult_error(
             controller: MFMailComposeViewController;
             result : MFMailComposeResult;
             error: NSErrorPtr);
Begin
  // to break the waiting loop
  done := true;
  EmailResult := result;
  //remove controler from screen
  controller.dismissModalViewControlleranimated(true);
end;

const MailDelegateVar : TMailDelegate=nil;
{$ENDIF}

constructor TiOSEmail.Create(AOwner: TComponent);
begin
  Inherited Create(Aowner);
  {$IFDEF FPC}
  if Assigned(MailDelegateVar) then
    raise Exception.Create('can only have one of these component');

  //allocate the delegate
	MailDelegateVar := TMailDelegate.alloc.init;
  {$ENDIF}
  //init property
  FSubject       := '';
  FRecipientsTo  := '';
  FRecipientsCC  := '';
  FRecipientsBCC := '';
  FBody          := '';
  FisHTML        := true;
  FFileAttachment:= TstringList.create;
end;

destructor TiOSEmail.Destroy;
begin
  {$IFDEF FPC}
  MailDelegateVar.release;
  MailDelegateVar := nil;
  {$ENDIF}
  //free objects
  FFileAttachment.free;
  inherited;
end;

function TiOSEmail.SendEmail: TEmailresult;
{$IFDEF FPC}
var NS : NSstring;
    anArray: NSMutableArray;
    tempresult : integer;
    i : integer;

  procedure addAttachement(filename:string);
  var attachment: NSData;
      mimeType: NSString;
      attName : string;
  Begin
    //addAttachmentData_mimeType_fileName(attachment: NSData; mimeType: NSString; filename: NSString)
    if FileExists(filename) then
    Begin
      attachment := NSData.alloc.initWithContentsOfFile( NSSTR(PChar(filename) ) );
      mimeType := NSSTR(PChar('text/plain') );
      filename := extractfilename(filename); //no need of the path anymore
      MailDelegateVar.Emailcontroler.addAttachmentData_mimeType_fileName( attachment, mimeType,NSSTR(PChar(attName)) );
      attachment.release;
    end;
  end;
  
{$ENDIf}
Begin
  {$IFDEF FPC}
  //set the email control and assign its delegate
  //log('Email start');
  MailDelegateVar.Emailcontroler := nil;
  MailDelegateVar.Emailcontroler := MFMailComposeViewController.alloc.init;

  if MailDelegateVar.Emailcontroler=nil then
  Begin
    result := erCannotSendEmail;
    //log('Email not alloced');
    exit;
  end;

  //log('Email cansend');
  try
    if MailDelegateVar.Emailcontroler.canSendMail=false then
    Begin
      result := erCannotSendEmail;
      //log('Email failed');
      MailDelegateVar.Emailcontroler.Release;
      exit;
    end;
  except
    //log('Email failed - ex');
    result := erCannotSendEmail;
    try
      MailDelegateVar.Emailcontroler.Release;
      //log('Email Release ok');
    except;
      exit;
    end;
  end;
  //ok we can send email
  //log('Email cansend ok');

  //log('Email set delegate');
  MailDelegateVar.Emailcontroler.setMailComposeDelegate(MailDelegateVar);

  //set subject
  NS := NSSTR(PChar(FSubject));
  MailDelegateVar.Emailcontroler.setSubject(NS);

  //recipients
  if trim(FRecipientsTo)<>'' then
  Begin
    NS  := NSSTR(PChar(FRecipientsTo));
    anArray:=NSMutableArray.arrayWithObjects( NS, nil);
    MailDelegateVar.Emailcontroler.setToRecipients(anArray);
  end;

  if trim(FRecipientsCC)<>'' then
  Begin
    NS  := NSSTR(PChar(FRecipientsCC));
    anArray:=NSMutableArray.arrayWithObjects( NS, nil);
    MailDelegateVar.Emailcontroler.setCCRecipients(anArray);
  end;

  if trim(FRecipientsBCC)<>'' then
  Begin
    NS  := NSSTR(PChar(FRecipientsBCC));
    anArray:=NSMutableArray.arrayWithObjects( NS, nil);
    MailDelegateVar.Emailcontroler.setBCCRecipients(anArray);
  end;

  //body
  MailDelegateVar.Emailcontroler.setMessageBody_isHTML( NSSTR(PChar(FBody)), FisHTML );
  
  //set attachement
  for i := 0 to FFileAttachment.count-1 do
    addAttachement(FFileAttachment[i]);

  //send
  MailDelegateVar.done := false;

  //display the email
  //log('Email show');
  mainWindow.mainController.presentModalViewController_animated(MailDelegateVar.Emailcontroler,true);
  //object passed to the presentModalViewController is no loonger needed
  MailDelegateVar.Emailcontroler.Release;

  //wait for email to compelte (or cancel)
  while not MailDelegateVar.done do
  Begin
    Application.processmessages;
    //while until finished
  end; 
  tempresult := MailDelegateVar.EmailResult;
  //Emailresult convert MFMailComposeResult to TEmailresult
  case tempresult of
    MFMailComposeResultCancelled : result := erCancelled;
    MFMailComposeResultSaved     : result := erSaved;
    MFMailComposeResultSent      : result := erSent;
    MFMailComposeResultFailed    : result := erFailed;
    else                           result := erUknown;
  end;
  {$ELSE}
  result := ersent;//erCannotSendEmail;//erCancelled;
  {$ENDIF}
end;

function iosSendEmail( recTo,recCC,recBCC,subject,body : string; //comma delimited
                       FileAttachment : TstringList = nil;
                       isHTML: boolean = true ): TEmailresult;
var anEmail: TiOSEmail;
Begin
  anEmail := TiOSEmail.create(nil);
  try
    anEmail.RecipientsTo  := recto;
    anEmail.recipientsCC  := recCC;
    anEmail.recipientsBCC := recBCC;
    anEmail.subject := subject;
    anEmail.body := body;
    anEmail.isHTML := isHTML;
    if FileAttachment<>nil  then
      anEmail.FileAttachment.text := FileAttachment.text;
    result := anEmail.sendEmail;
  finally
    anEmail.free;
  end;
end; 


procedure Register;
begin
  RegisterComponents('iOS', [TiOSEmail]);
end;

end.

Open in new window

The Question

The component works great from my standpoint excepting that, as one Delphi Developer Member pointed out, the email attachment receives a random file name (i.e., “ATT0114.TXT”) and try as I might, I haven’t been able to find a way to assign a meaningful file name. This issue has languished since June with no resolution in sight.

Pages is able to do this, so apparently it can be done. My question is can this component be easily modified to allow naming the file?
ASKER CERTIFIED SOLUTION
Avatar of developmentguru
developmentguru
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of efz

ASKER

If you notice in line 197 the author uses the variable "attName" which is not (as far as I can tell) initialized prior to the call in line 197. Unfortunately, when I gave it a value equal to the file name, it used the random name anyway. For example, I tried this line just prior to line 197:

attName:=ExtractFileName(FileName);

Actually, I may be misstating this problem. I think what happens is the iOS doesn't give the attachment any name and the name is being assigned by Outlook (the recipient's email handler).

In other words, when the attachment appears in the iPad email handler, no name appears below the attachment icon. In Pages, that's where the attachment name would typically appear.

Based on my misinformation, you may be incorrect when you say it looks like the file name was being randomized by the iOS. I can't tell whether iOS is naming the attachment after the Send button is pressed or whether Outlook is naming the attachment because it has no name when it receives the email. Does this make any sense?
Line 272 is where it is adding the attachments.  What fills the list?  Whether it is being named by iOS or by Outlook, it is being named by something outside of this code.

Here is an example...

If you were to create your own event... say... before line 272 where the list of attachments is being added... you could have code in the event handler fill a list with names for those files.  The problem is this: How would you know what to name them?  The names are already butchered by this point.

I hope that helps to clarify my earlier statement.
You may need another portion of your app that will act as the email handler to see how this fits together.  Doing so would allow you the control that you currently lack.
Avatar of efz

ASKER

Your advice, coupled with a stupid error on my part, AND your observation that the code seemed "quite straightforward" and therefore probably correct led me to the solution. Please accept my sincere thanks.