efz
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.
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?
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.
The QuestionThe 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
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
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.
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.
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.
ASKER
attName:=ExtractFileName(F
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?