MFMailComposeViewController in a separate class - iphone

I seek to create a "Utility Email sender class" that I can use in several iPhone projects.
I created MailSender header and implementation for that purpose.
MailSender.h:
#interface MailSender : NSObject<MFMailComposeViewControllerDelegate>
- (id) initWithParent:(UIViewController*) mainController;
- (void) invokeMailSender:(NSString*) to:(NSString*) subject:(NSString*) failureTitle:(NSString*) failureMessage:(NSString*) failureCancel;
#end
MailSender.m:
#import "MailSender.h"
#implementation MailSender
MFMailComposeViewController* mailer;
UIViewController* mailParentController;
- (id) initWithParent:(UIViewController*) mainController
{
if( self = [super init])
{
mailParentController = mainController;
}
return self;
}
- (void) invokeMailSender:(NSString*) to:(NSString*) subject:(NSString*) failureTitle:(NSString*) failureMessage:(NSString*) failureCancel;
{
if([MFMailComposeViewController canSendMail])
{
mailer = [[MFMailComposeViewController alloc] init];
mailer.mailComposeDelegate = self;
[mailer setSubject:subject];
NSArray *toRecipients = [NSArray arrayWithObjects:to, nil];
[mailer setToRecipients:toRecipients];
[mailParentController presentModalViewController:mailer animated:YES];
}
else
{
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:failureTitle message:failureMessage
delegate:nil cancelButtonTitle:failureCancel otherButtonTitles: nil];
[alert show];
}
}
-(void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error
{
// Do nothing
[mailParentController dismissModalViewControllerAnimated:YES];
mailer = nil;
}
#end
I called the class from a View Controller (in a button touch down action) using the following instructions:
#implementation InfoViewController
MailSender *sender;
- (IBAction)openMail:(id)sender
{
sender = [[MailSender alloc] initWithParent:self];
[sender invokeMailSender:#"test#test.com" :#"123" :#"123" :#"123" :#"123"];
}
....
#end
When I run the code, I am able to show the email views correctly. However, this is then followed by a crash.
Note that I do not have a crash when using MFMailComposeViewController directly from my UIViewController (And assigning the View Controller as the delegate),
Any ideas?
Sorry I am still a new to Objective C :)

You need to retain your sender MailSender instance. It is being released after you call the invoke message.
You could do this by declaring a property named sender. E.g.
#property (strong, nonatomic) MailSender *sender;
...
#synthesize sender = _sender;
...
self.sender = [[MailSender alloc] initWithParent:self];
[self.sender invokeMailSender:#"noor#dimachk.com" :#"123" :#"123" :#"123" :#"123"];
By the way, your method declaration is a bit funny. You should name the arguments. E.g.
- (void)invokeMailSender:(NSString *)sender
to:(NSString *)to
subject:(NSString *)subject
failureTitle:(NSString *)failureTitle
failureMessage:(NSString *)failureMessage
failureCancelButtonTitle:(NSString *)failureCancelButtonTitle

Related

Ios MFMailComposeViewController not displaing

I trying to write rhodes native extension extension for opening iOS MailComposer.
Now code "works", but MFMailComposeViewController not displaying and xcode shows warning:
Attempt to present <MFMailComposeViewController: 0x12b60840> on <Iosmailto: 0xc1898d0> whose view is not in the window hierarchy!
I read that UIViewControllers must be in hierarchy, but i can't provide this from Rhodes ruby code or clear-C extension wrapper.
Now i'm trying to present my UIViewController with MFMailComposeController from [UIApplication sharedApplication].keyWindow.rootViewController but mail composer not show yet.
interface from iosmailto.h:
#interface Iosmailto : UIViewController<MFMailComposeViewControllerDelegate>
{
}
#property(nonatomic, assign) id delegate;
//-(IBAction)send_mail:(id)sender;
#end
implementation from iosmailto.m:
#implementation Iosmailto
- (void)viewDidLoad {
[super viewDidLoad];
[self send_mail];
}
- (void)send_mail {
if ([MFMailComposeViewController canSendMail]) {
MFMailComposeViewController *composer = [[MFMailComposeViewController alloc] init];
composer.mailComposeDelegate = self;
[composer setSubject:[NSString stringWithUTF8String:subj]];
NSArray *recipients_array = [NSArray arrayWithObject:[NSString stringWithUTF8String:recipients]];
[composer setToRecipients:recipients_array];
NSString *composerBody = [NSString stringWithUTF8String:message];
[composer setMessageBody:composerBody isHTML:NO];
[composer setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
[self presentModalViewController:composer animated:YES];
[composer release];
} else {
NSLog(#"Device is unable to send email in its current state.");
}
}
/* some unnecessary for this question methods */
#end
And C function defined in iosmailto.m thats called from Rhodes:
// find root vc
UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
// find topmost vc
while (topController.presentedViewController) {
topController = topController.presentedViewController;
}
iosmailer = [[Iosmailto alloc] init];
iosmailer.delegate = topController;
[topController presentViewController:iosmailer animated:YES completion:nil];
This app is not for AppStore. Hacks is welcome if needed.
Update init for Iosmailto:
- (id)init
{
self = [super initWithNibName:nil bundle:nil];
return self;
}
Update 2 The short version of this code (without UIViewController wrapper) was my starting point. That's don't work too. And this have the same warning and one more:
UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
while (topController.presentedViewController) {
topController = topController.presentedViewController;
}
if ([MFMailComposeViewController canSendMail]) {
MFMailComposeViewController *composer = [[MFMailComposeViewController alloc] init];
composer.mailComposeDelegate = (id<MFMailComposeViewControllerDelegate>)topController ;
[composer setSubject:[NSString stringWithUTF8String:subj]];
NSArray *recipients_array = [NSArray arrayWithObject:[NSString stringWithUTF8String:recipients]];
[composer setToRecipients:recipients_array];
NSString *composerBody = [NSString stringWithUTF8String:message];
[composer setMessageBody:composerBody isHTML:NO];
[composer setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
[topController presentModalViewController:composer animated:YES];
[composer release];
} else {
}
Use this code to present view controller
[[[[[UIApplication sharedApplication] delegate] window] rootViewController] presentViewController:composer
animated:YES
completion:nil];
Ok I think the problem is that your UIViewController doesn't have anything in its view property because you aren't using a .xib file to initialize it. See this question which programmatically creates a UIView and assigns it to the UIViewController's view property. I think that's the right way to go. See the implementation of their -(void)loadView method.
Your use-case is also a little strange, since it looks like your Iosmailto UIViewController doesn't have any content, and you're just using it as a wrapper around MFMailComposeViewController--you might consider reimplementing your C method to directly create a MFMailComposeViewController without the unnecessary layer of indirection of this unnecessary UIViewController that doesn't display anything itself.

Presenting a modal view from another class (MFMailComposeViewController)

I am trying to create a class who will be responsible to send emails using MFMailComposeViewController so i can use this methods from differences views controls in my app.
This class is called apoio.
In this class have the method bellow.
-(void) enviarGraficoPorEmail: (NSData*) _pdfGrafico {
if (![MFMailComposeViewController canSendMail]) {
// show message box for user that SMS cannot be sent
} else {
MFMailComposeViewController *picker = [[MFMailComposeViewController alloc] init];
picker.mailComposeDelegate = self;
[picker setSubject:#"Dashboard"];
[picker addAttachmentData:_pdfGrafico mimeType:#"application/pdf" fileName:#"grafico.pdf"];
NSString *emailBody = #"Anexando gráfico";
[picker setMessageBody:emailBody isHTML:NO];
[self presentModalViewController:picker animated:YES];
}
}
I have another view controller who calls the apoio method when the user clicks on email button. It is this code bellow
-(IBAction) enviarGraficoPorEmail {
Apoio *apoio = [[Apoio alloc] init];
[apoio enviarGraficoPorEmail:[barChart dataForPDFRepresentationOfLayer]];
}
But i don't know why, the email view doesn't appear. The method is called correct because i debuged and so on.
If i copy the code from apoio method to enviarGraficoPorEmail method, everything works perfect.
But i don't want to do this, beucase ill send emails from others views controller.
What am i doing wrong ??
You could do it a couple of different ways.
Option 1: Pass the calling view controller as a parameter to the class method
-(IBAction) enviarGraficoPorEmail {
Apoio *apoio = [[Apoio alloc] init];
[apoio enviarGraficoPorEmail:[barChart dataForPDFRepresentationOfLayer] callingController:self];
}
-(void) enviarGraficoPorEmail: (NSData*) _pdfGrafico callingController:(UIViewController*)_callingController {
...
[_callingController presentModalViewController:picker animated:YES];
...
}
Option 2: Add a class variable for the calling view controller
-(IBAction) enviarGraficoPorEmail {
Apoio *apoio = [[Apoio alloc] init];
apoio.callingController = self;
[apoio enviarGraficoPorEmail:[barChart dataForPDFRepresentationOfLayer]];
}
-(void) enviarGraficoPorEmail: (NSData*) _pdfGrafico callingController:(UIViewController*)_callingController {
...
[callingController presentModalViewController:picker animated:YES];
...
}
Then you'd add callingController to your class as a retain property, initialize it to nil, and release it in dealloc.
Option #1 is probably the better approach for your needs.
Thanks a lot!. It is now working but i still have one problem.
I have the method on my generic class who is supposed to be responsible to hide the mailController.
- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error
{
switch (result)
{
case MFMailComposeResultCancelled:
break;
case MFMailComposeResultSaved:
break;
case MFMailComposeResultSent:
// FAILS
[self.parentViewController dismissModalViewControllerAnimated:YES];
break;
case MFMailComposeResultFailed:
break;
default:
break;
}
[self dismissModalViewControllerAnimated:YES];
}
In the method that creates the mailController have the property
picker.mailComposeDelegate = self;
I tried to change to
picker.mailComposeDelegate = _callingController.self;
I set the MFMailComposeViewControllerDelegate on my generic class already.
But it only works when i copy the method didFinishWithResult and put it on the origin controller, what is not my intention, because i want to put all this code on a generic class.
What am i doing wrong ??
Ok, this is an answer to your second question (which you posted as an answer to your first question).
Here's how I would set it all up:
In your calling view controller .h file:
#interface MyViewController : UIViewController <MyMailDelegate> {
Apoio *apoio;
}
In your calling view controller .m file:
-(IBAction) enviarGraficoPorEmail {
apoio = [[Apoio alloc] init];
apoio.callingController = self;
[apoio enviarGraficoPorEmail:[barChart dataForPDFRepresentationOfLayer]];
}
-(void) enviarCompleto {
//do whatever here after send email completes
[apoio release];
}
In your Apoio .h file
#protocol MyMailDelegate
#required
-(void) enviarCompleto;
#end
#interface OfferObject : NSObject {
UIViewController <MyMailDelegate> *callingController;
}
#property (nonatomic, retain) UIViewController <MyMailDelegate> *callingController;
In your Apoio .m file
-(void) enviarGraficoPorEmail: (NSData*) _pdfGrafico callingController:(UIViewController*)_callingController {
...
[callingController presentModalViewController:picker animated:YES];
...
}
-(void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error
{
switch (result)
{
...
}
[callingController dismissModalViewControllerAnimated:YES];
[callingController enviarCompleto];
}
Then don't forget to do this on init:
callingController = nil;
And on dealloc:
[callingController release];
Also, don't forget your most important step: up vote both my answers :)
You need to call [self presentModalViewController:picker animated:YES];
from a UIViewController class then it will show you email composer, you can do this, whenever you call from some view controller you can pass its reference and you can change the above line as this
[callingController presentModalViewController:picker animated:YES];

didFinishWithResult is not getting invoked for MFMailComposeViewController

in buttonpress callback:
MFMailComposeViewController *mailViewController = [[MFMailComposeViewController alloc] init];
mailViewController.mailComposeDelegate = self;
[self presentModalViewController:mailViewController animated:YES];
Delegate Implementation:
- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error {
NSLog (#" Inside MAIL COMPOSER CONTROLLER DELIGATE ");
// Remove the mail view
[self dismissModalViewControllerAnimated:YES];
}
When i press cancel button in the MailComposerView, delete is not getting invoked. what am i doing wrong ?
Set your viewController as a MFMailComposeViewControllerDelegate:
#interface CurrentViewController : UIViewController <MFMailComposeViewControllerDelegate>
Set your mailComposer delegate right after instantiation:
MFMailComposeViewController * mailComposer = [[MFMailComposeViewController alloc]init];
mailComposer.mailComposeDelegate = self;
Did you actually make your class a MFMailComposeViewControllerDelegate?
#interface MyViewController : UIViewController <MFMailComposeViewControllerDelegate>

Can not Edit Address Book Record from iphone app

I want to edit record from address book in my iphone app. But I can not edit any record. Here is my code
// In my First View Controller
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
DetailViewController *detailViewController=[[DetailViewController alloc] initWithRecordObject:record];
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
}
//------------------------------------
#import <UIKit/UIKit.h>
#import <AddressBook/AddressBook.h>
#import <AddressBookUI/AddressBookUI.h>
#import "User.h"
#interface DetailViewController : UIViewController <UITableViewDelegate,UITableViewDataSource,UIActionSheetDelegate,ABPersonViewControllerDelegate> {
ABRecordRef record;
// Some other ivars
}
- (id)initWithRecordObject:(ABRecordRef)myrecord;
//------------------------------------
#implementation DetailViewController
- (id)initWithRecordObject:(ABRecordRef)myrecord
{
self = [super initWithNibName:#"DetailViewController" bundle:nil];
if (self) {
record = myrecord;
}
return self;
}
#pragma mark - Edit Record Method
-(void)btnEditContactTapped:(id)sender {
// Fetch the address book
ABAddressBookRef addressBook = ABAddressBookCreate();
ABRecordID recID = ABRecordGetRecordID(record);
ABRecordRef record1 = ABAddressBookGetPersonWithRecordID(addressBook,recID);
ABPersonViewController *personViewController = [[ABPersonViewController alloc]init];
// set delegate
personViewController.personViewDelegate = self;
// Allow editing info
personViewController.allowsEditing = YES;
// Display contact info of selected person
personViewController.displayedPerson = record1;
personViewController.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"Back" style:UIBarButtonItemStylePlain target:self action:#selector(returnFromPersonView)] ;
APP_DELGATE.isContactEdited = YES;
[self.navigationController pushViewController:personViewController animated:YES];
[personViewController release];
}
-(void)returnFromPersonView {
NSLog(#"In %s",__PRETTY_FUNCTION__);
[self.navigationController popViewControllerAnimated:YES];
}
#pragma mark - ABPersonViewControllerDelegate Method
- (BOOL)personViewController:(ABPersonViewController *)personViewController shouldPerformDefaultActionForPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifierForValue {
//[self dismissModalViewControllerAnimated:YES];
return NO;
}
When I push personViewController , I can't see anything regarding record. Here is a screenshot
Any kind of help is highly appreciated.Thanks
Take a look at Apple's Adress Book Programming Guide. A page that would be useful to you is the Direct Interaction: Programmatically Accessing the Database page. I advise you to just look around at those pages, they are quite self explanatory.
Changing return to YES may help in last method
- (BOOL)personViewController:(ABPersonViewController *)personViewController shouldPerformDefaultActionForPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifierForValue {
//[self dismissModalViewControllerAnimated:YES];
return NO; //try changing it to YES
}
You are not passing the "Correct Contact ID" as ABRecordRef ;)

Best way to pass variables between views

I am very new to xcode & Objective-C (having come from PHP) i have started to play around and am finding it very hard to pass variables between views this is what i have so far:
Game_info.h
#import <UIKit/UIKit.h>
#interface Game_Info : UIViewController {
IBOutlet UITextField *groupName;
IBOutlet UISegmentedControl *gameType;
}
#property (nonatomic,retain) IBOutlet UITextField *groupName;
- (IBAction) GameTypePicker;
- (IBAction) KeyboardHide;
- (IBAction) BackBTN;
- (IBAction) NextBTN;
#end
Game_Info.m
#import "I_Dare_YouViewController.h"
#import "Game_Info.h"
#import "Game_TDoR.h"
#implementation Game_Info
#synthesize groupName;
// Next Button
-(IBAction) NextBTN{
if ([groupName.text length] == 0) {
// Alert
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"Group Name"
message:#"Please enter a group name"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil
];
[alert show];
[alert release];
}else if([groupName.text length] < 3){
// Alert
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"Group Name"
message:#"Please enter a name longer than 3 characters"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil
];
[alert show];
[alert release];
}else{
Game_TDoR *screen = [[Game_TDoR alloc] initWithNibName:nil bundle:nil];
screen.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:screen animated:YES];
[screen release];
Game_TDoR *screen1 = [[Game_TDoR alloc] initWithNibName:nil bundle:nil];
NSLog(#"Game_Info: %#",self.groupName.text);
screen1.groupNameText = self.groupName.text;
[self presentModalViewController:screen1 animated:YES];
[screen1 release];
}
}
Then in another view / .h / .m file i am trying to get to the 'groupName' property.
Game_TDoR.m
#import "I_Dare_YouViewController.h"
#import "Game_Info.h"
#import "Game_TDoR.h"
#implementation Game_TDoR
#synthesize groupNameText,Testlbl;
- (void)viewDidLoad
{
NSLog(#"Game_TDoR: %#",self.groupNameText);
NSString *msg = [[NSString alloc] initWithFormat:#"Hello, %#",[self.groupNameText capitalizedString]];
[Testlbl setText:msg];
[msg release];
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
So what i am trying to do is on the first view page (Game_info) there is an input text box and im trying to pass that to a label on another view page (Game_TDoR)
This is what comes out in the NSLog (note that the second page (Game_TDoR) comes out first in the log.
2011-07-17 00:25:34.765 I Dare You[3941:207] Game_TDoR: (null)
2011-07-17 00:25:34.774 I Dare You[3941:207] Game_Info: Name
Problem solved:
On the next button i needed to add the variable before i moved page (not the other way around - silly noobish thing to do...)
Game_TDoR *screen1 = [[Game_TDoR alloc] initWithNibName:nil bundle:nil];
NSLog(#"Game_Info: %#",self.groupName.text);
screen1.groupNameText = self.groupName.text;
[self presentModalViewController:screen1 animated:YES];
[screen1 release];
Game_TDoR *screen = [[Game_TDoR alloc] initWithNibName:nil bundle:nil];
screen.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:screen animated:YES];
[screen release];
If you need to pass the value of groupName.text from the Game_Info view controller to the Game_TDoR view controller, which is presented modally by the former, you can declare a property in Game_TDoR to hold the value:
1) Declare a NSString property in Game_TDoR #interface block:
#property (nonatomic,copy) NSString *groupNameText;
(Remember to synthesize or implement the accessor methods in the implementation block)
2) In NextBTN action, after you initialize your Game_TDoR instance, set the property:
Game_TDoR *screen = [[Game_TDoR alloc] init...];
screen.groupNameText = self.groupName.text;
Merely including the header file will not make the variable available. You are only including the definition of the class (class is for PHP what #interface is for Obj-C) You have to instantiate Game_Info and access the variable via the instance of Game_Info.
groupName is a member (ivar) of Game_Info. Its default visibility is #protected, so any classes outside Game_Info can't access it, unless they derive from Game_Info. To make groupName accessible, you can either create a property that exposes it, or you can make it #public. How this is done is documented in the vast documentation for Xcode.
But groupName, being an ivar (instance variable) of an object, only exists if there is in fact an instance of the Game_Info. I assume you have some globally accessible instance of Game_Info in your program, let's call it globalGameInfo. Now you can access its groupName using
UITextField *gName = [globalGameInfo groupName];
Take this in .h file in SecondViewController
NSString *strABC;
Make below function in SecondViewController.m
-(void)setString:(NSString *)strEntered{
strABC=strEntered;
}
Now In First view controller do like this:
SecondViewController.m *objSecond = [[SecondViewController.m] initwithNibName:#"secondView.xib" bundle:nil];
[objSecond setString:#"Comment Controller"];
[self.navigationController pushViewController:objSecond animated:YES];
[objSecond release];
Now, In secondViewController viewWillAppear Method write this.
-(void)viewWillAppear:(BOOL)animated{
lblUserInput.text = strABC;
}
Please check spelling mistakes as I hand written this. Hope this help.
If you are not using navigationContoller then you can do something like this.
SecondViewControler *objSecond = [[SecondViewController] initwithNibName:#"secondview.xib" bundle:nil];
[objSecond setUserInput:txtUserInput.text];
[objSecond viewWillAppear:YES];
[self.view addSubview:objSecond];
[objSecond release];