I am calling MFMailComposeViewController from a UITableViewController.
Problem is the delegate method is never called when I select Cancel or Send button in Mail compose window:
mailComposeController:(MFMailComposeViewController*)controllerdidFinishWithResult
Here is the table view class:
#implementation DetailsTableViewController
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section==0 && indexPath.row==4) {
//SEND MAIL
MFMailComposeViewController *controller = [[MFMailComposeViewController alloc] init];
controller.mailComposeDelegate = self;
if ([MFMailComposeViewController canSendMail]) {
[controller setSubject:[NSString stringWithFormat:#"Ref %#",[item objectForKey:#"reference"]]];
[controller setMessageBody:#" " isHTML:NO];
[controller setToRecipients:[NSArray arrayWithObject:[item objectForKey:#"email"]]];
[self presentModalViewController:controller animated:YES];
}
[controller release];
}
}
- (void)mailComposeController:(MFMailComposeViewController*)controllerdidFinishWithResult:(MFMailComposeResult)result error:(NSError*)error {
// NEVER REACHES THIS PLACE
[self dismissModalViewControllerAnimated:YES];
NSLog (#"mail finished");
}
The application doesn't crash. After the Cancel or Send button is pressed, the Compose Window stays on the screen with buttons disabled. I can exit application pressing Home key.
I am able to open other Modal Views form TableView but not MailCompose.
Make sure you use
controller.mailComposeDelegate = self;
and not
controller.delegate = self;
Your method signature is incorrect:
- (void)mailComposeController:(MFMailComposeViewController*)controllerdidFinishWithResult:(MFMailComposeResult)result error:(NSError*)error
Should be:
- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error
Refer this article for full implementation : http://www.ioscreator.com/tutorials/send-email-from-an-app
working code after making removing deprecated one :
#import <MessageUI/MFMailComposeViewController.h>
#interface SettingsTableViewController () <MFMailComposeViewControllerDelegate, UITextFieldDelegate, UITextViewDelegate>
#end
#implementation SettingsTableViewController
// add default methods
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSInteger sectionNum = indexPath.section;
NSInteger rowNum = indexPath.row;
if (sectionNum == 2 && rowNum == 1) {
MFMailComposeViewController *controller = [[MFMailComposeViewController alloc] init];
controller.mailComposeDelegate = self;
if ([MFMailComposeViewController canSendMail]) {
[controller setSubject:[NSString stringWithFormat:#"Invitation to Northstar app"]];
[controller setMessageBody:#" " isHTML:NO];
// [controller setToRecipients:[NSArray arrayWithObject:[item objectForKey:#"email"]]];
//presentViewController:animated:completion:
[self presentViewController:controller animated:YES completion:NULL];
}
}
}
- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error
{
NSLog (#"mail finished");
[self dismissViewControllerAnimated:YES completion:NULL];
}
#end
I faced the same problem and was searching for a fix from past 2 days then I found a fix myself and you won't believe how minor it was.
In my case the view controller (say 'DetailsTableViewController' as per this question) from where I was presenting the MFMailComposeViewController is already being presented from some other view controller (say 'BaseViewController').
The issue was lying in the 'modalPresentationStyle' of 'DetailsTableViewController' while presenting it from BaseViewController.
The moment I changed it from 'UIModalPresentationFormSheet' to 'UIModalPresentationPageSheet' (for that matter any thing other than 'UIModalPresentationFormSheet') issue got resolved and mail controller delegate methods started firing as usual.
Note: I was already calling the below method in 'DetailsTableViewController' (for this example) so it didn't really matter for me which 'modalPresentationStyle' I was using.
- (void)viewWillLayoutSubviews{
[super viewWillLayoutSubviews];
self.view.superview.bounds = CGRectMake(0, 0, 1024, 768);
self.view.superview.backgroundColor = [UIColor clearColor];
}
Related
So....I have a View Controller and when I press a button, another View Controller appears:
- (IBAction)searchButtonPressed:(id)sender {
[self presentViewController:self.controllerSearch animated:YES completion:nil];
}
Inside view controller number 2 is a table view and when a row is selected in a table this code runs:
NSString *phrase = nil; // Document password (for unlocking most encrypted PDF files)
NSString *filePath2 = filePath; assert(filePath2 != nil); // Path to first PDF file
LazyPDFDocument *document = [LazyPDFDocument withDocumentFilePath:filePath2 password:phrase];
if (document != nil) // Must have a valid LazyPDFDocument object in order to proceed with things
{
LazyPDFViewController *lazyPDFViewController = [[LazyPDFViewController alloc] initWithLazyPDFDocument:document];
lazyPDFViewController.delegate = self; // Set the LazyPDFViewController delegate to self
#if (DEMO_VIEW_CONTROLLER_PUSH == TRUE)
[self.navigationController pushViewController:lazyPDFViewController animated:YES];
#else // present in a modal view controller
lazyPDFViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
lazyPDFViewController.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:lazyPDFViewController animated:YES completion:NULL];
#endif // DEMO_VIEW_CONTROLLER_PUSH
}
else // Log an error so that we know that something went wrong
{
NSLog(#"%s [LazyPDFDocument withDocumentFilePath:'%#' password:'%#'] failed.", __FUNCTION__, filePath2, phrase);
}
Now I am using LazyPDFKit and it comes with this delegate method:
- (void)dismissLazyPDFViewController:(LazyPDFViewController *)viewController
{
// dismiss the modal view controller
[self dismissViewControllerAnimated:YES completion:NULL];
}
I put a break point and I can see my code goes into the delegate method, but the LazyPDFViewController does not go away.
I have tried the following:
[[[self presentingViewController] presentingViewController] dismissViewControllerAnimated:YES completion:nil];
but that takes me back a few view controllers to far.
Am I missing something?
Additional code in my first view Controller .h
#property (strong, nonatomic) UISearchController *controllerSearch;
and in first view controller .m
- (UISearchController *)controller {
if (!_controllerSearch) {
// instantiate search results table view
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Storyboard" bundle:nil];
LHFileBrowserSearch *resultsController = [storyboard instantiateViewControllerWithIdentifier:#"SearchResults"];
// create search controller
_controllerSearch = [[UISearchController alloc]initWithSearchResultsController:resultsController];
_controllerSearch.searchResultsUpdater = self;
// optional: set the search controller delegate
_controllerSearch.delegate = self;
}
return _controllerSearch;
}
If you are pushing the view controller:
[self.navigationController pushViewController:lazyPDFViewController animated:YES];
Then the code in the delegate doesn't make sense, because it assumes it is a modal view controller that needs to be dismissed:
- (void)dismissLazyPDFViewController:(LazyPDFViewController *)viewController
{
// dismiss the modal view controller
[self dismissViewControllerAnimated:YES completion:NULL];
}
But you've added it to the navigation stack (I assume).
If you can't pop it again from the navigation controller at this point you are missing some code in your example.
Are you sure your delegate is firing on the main thread? Try:
- (void)dismissLazyPDFViewController:(LazyPDFViewController *)viewController
{
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController popViewControllerAnimated:YES];
});
}
try this:
- (void)dismissLazyPDFViewController:(LazyPDFViewController *)viewController
{
// dismiss the modal view controller
[[viewController presentingViewController] dismissViewControllerAnimated:YES completion:nil];
}
your code :
[[[self presentingViewController] presentingViewController] dismissViewControllerAnimated:YES completion:nil];
just went too far.
I just made the demo project based on your situation. And I am not facing any issue with it. So I think there might be some issue regarding how you are presenting the second controller.
In your button click, try this code:
- (IBAction)searchButtonPressed:(id)sender {
UIStoryboard *main = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
//idSecondVC is the storyboard id of second view controller
SecondVC *SecondVC = [main instantiateViewControllerWithIdentifier:#"idSecondVC"];
[self presentViewController:SecondVC animated:YES completion:nil];
}
And in your controller number 2, I just used the above code:
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 10;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
cell.textLabel.text = [NSString stringWithFormat:#"Cell %ld",indexPath.row];
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self openLazyPDF];
}
- (void)openLazyPDF
{
NSString *phrase = nil; // Document password (for unlocking most encrypted PDF files)
NSArray *pdfs = [[NSBundle mainBundle] pathsForResourcesOfType:#"pdf" inDirectory:nil];
NSString *filePath = [pdfs firstObject]; assert(filePath != nil); // Path to first PDF file
LazyPDFDocument *document = [LazyPDFDocument withDocumentFilePath:filePath password:phrase];
if (document != nil) // Must have a valid LazyPDFDocument object in order to proceed with things
{
LazyPDFViewController *lazyPDFViewController = [[LazyPDFViewController alloc] initWithLazyPDFDocument:document];
lazyPDFViewController.delegate = self; // Set the LazyPDFViewController delegate to self
#if (DEMO_VIEW_CONTROLLER_PUSH == TRUE)
[self.navigationController pushViewController:lazyPDFViewController animated:YES];
#else // present in a modal view controller
lazyPDFViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
lazyPDFViewController.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:lazyPDFViewController animated:YES completion:NULL];
#endif // DEMO_VIEW_CONTROLLER_PUSH
}
else // Log an error so that we know that something went wrong
{
NSLog(#"%s [LazyPDFDocument withDocumentFilePath:'%#' password:'%#'] failed.", __FUNCTION__, filePath, phrase);
}
}
#pragma mark - LazyPDFViewControllerDelegate methods
- (void)dismissLazyPDFViewController:(LazyPDFViewController *)viewController
{
// dismiss the modal view controller
[self dismissViewControllerAnimated:YES completion:NULL];
}
And for me everything is working fine.
Looks like you need to the same macro for present as dismiss. So, you wrote
#if (DEMO_VIEW_CONTROLLER_PUSH == TRUE)
[self.navigationController pushViewController:lazyPDFViewController animated:YES];
#else // present in a modal view controller
lazyPDFViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
lazyPDFViewController.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:lazyPDFViewController animated:YES completion:NULL];
#endif // DEMO_VIEW_CONTROLLER_PUSH
You thus need
#if (DEMO_VIEW_CONTROLLER_PUSH == TRUE)
[self.navigationController popViewControllerAnimated:YES];
#else // presented in a modal view controller
[self dismissViewControllerAnimated:YES completion:NULL];
#endif // DEMO_VIEW_CONTROLLER_PUSH
It's possible that you have switched off the main thread and you can always add an assert to be check or, as has been suggested, use a dispatch_async to be certain.
NSAssert([NSThread isMainThread)];
I prefer the assert, when I know all the flows through a piece of code, since it shows my assumptions to the future me (or another) and does not leave code that looks like it knows something I do not (oh, they are using dispatch_async onto main so there must be some other thread magic going on deeper down).
- (void)dismissLazyPDFViewController:(LazyPDFViewController *)viewController
{
if (![NSThread isMainThread])
{
dispatch_async(dispatch_get_main_queue(), ^
{
[self dismissLazyPDFViewController:viewController];
});
return;
}
if (viewController.navigationController)
{
[viewController.navigationController popViewControllerAnimated:YES];
}
else
{
[viewController dismissViewControllerAnimated:YES completion:nil];
}
}
I have ViewController with button, and action on button:
- (IBAction)clickMe:(id)sender {
MailHelper *helper = [[MailHelper alloc] init];
[helper setAllData:self];
}
Also, there is helper class for mail composing (MailHelper.h):
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <MessageUI/MessageUI.h>
#interface MailHelper : UIViewController<MFMailComposeViewControllerDelegate>
#property MFMailComposeViewController* mailView;
- (void)setAllData:(UIViewController *)ctrl;
#end
and implementation (MailHelper.m):
- (void)setAllData:(UIViewController *)ctrl {
mailView = [[MFMailComposeViewController alloc] init];
mailView.mailComposeDelegate = self;
mailView.toRecipients = #[#"mail#email.com"];
[mailView setSubject:#"Subject"];
[ctrl presentViewController:mailView animated:YES completion:nil];
}
-(void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error {
[self dismissViewControllerAnimated:YES completion:nil];
}
I can open mail composer, but while sending mail, saving draft or deleting draft app crashes. Any ideas?
When the MFMailComposeViewController is request to be dismissed you are calling the dismiss method on self but self is presenting te MFMailComposeViewController.
Change:
-(void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error {
[self dismissViewControllerAnimated:YES completion:nil];
}
to
-(void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error {
[controller dismissViewControllerAnimated:YES completion:nil];
}
To fix the deallocation issue add the following:
import the <objc/runtime.h> in you .m file and make self be associated with the controller passed:
static void * MailHelperKey = &MailHelperKey;
- (void)setAllData:(UIViewController *)ctrl {
mailView = [[MFMailComposeViewController alloc] init];
mailView.mailComposeDelegate = self;
objc_setAssociatedObject(mailView, MailHelperKey, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
....
}
-(void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error {
[controller dismissViewControllerAnimated:YES completion:nil];
objc_removeAssociatedObjects(controller);
}
This will make sure that as long a the presenting control is not deallocated your mail helper. Does not deserve on price fro beauty but should work.
Ok guys. Solution is to make instance of helper somewhere else, let's say:
#implementation ViewController
MailHelper *helper;
and:
- (IBAction)clickMe:(id)sender {
helper = [[MailHelper alloc] init];
[helper setAllData:self];
}
I'm currently developing an app where I have a static tableView with 7 sections. These sections are different courses that the user can apply to and what I want to accomplish is when the user clicks the row / button of a section. It pushes a MailComposerView and inside the mail It says for example,
Hi I would like to apply to course %# which accrues at this date %#. The %# being the selected course and date.
Pleas tell me If I need to add more information / code.
Thanks.
You will need to make use of the didSelectRowAtIndexPath: method of your UITableView. Try something like below:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
MFMailComposeViewController *mailViewController = [[MFMailComposeViewController alloc] init];
mailViewController.mailComposeDelegate = self;
[mailViewController setSubject:#"Course Apply"]; //Set the subject here
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; // this gets the currently selected cell.. Hope in your case, it's a custom cell.
[mailViewController setMessageBody:[NSString stringWithFormat:#"Hi I would like to apply to course %# which accrues at this date %#",cell.courseSelected,cell.courseData] isHTML:NO]; //courseSelected and courseDate are properties that hold value in your custom cell
NSArray *toRecipients = [NSArray arrayWithObject:#"your.email#email.com"];
[mailViewController setToRecipients:toRecipients]; // set the recipient address here
[self presentViewController:mailViewController animated:YES completion:nil]; // and finally present it...
}
Hope this helps..
Import <MessageUI/MessageUI.h> and <MessageUI/MFMailComposeViewController.h> into your viewController and add the MFMailComposeViewControllerDelegate to your Interface.
Use this code to show MailComposeView:
MFMailComposeViewController *mailViewController = [[MFMailComposeViewController alloc] init];
mailViewController.mailComposeDelegate = self;
[mailViewController setSubject:#"<subject here>"]
[mailViewController setMessageBody:#"" isHTML:NO];
NSArray *toRecipients = [NSArray arrayWithObject:#"your.email#email.com"];
[mailViewController setToRecipients:toRecipients];
[self presentViewController:mailViewController animated:YES completion:nil];
And this to dismiss the MailComposeView:
-(void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error
{
NSLog(#"ERROR: %#", error);
[self dismissViewControllerAnimated:YES completion:nil];
}
If you want to have different body text then put the code in a method which accepts NSString parameter and call that from the button or the selection method of the row and give it the text you want to have in the body.
So, I think that when I click outside of a popover, the method popoverControllerDidDismissPopover should be called. I know this isn't called when dismissPopoverAnimated is called.
I have a simple project that I have setup that shows popoverControllerDidDismissPopover just isn't called:
#import "ViewController.h"
#import "PopoverViewController.h"
#interface ViewController ()
{
PopoverViewController *controller;
UIPopoverController *popoverController;
}
#end
#implementation ViewController
#synthesize button;
- (IBAction)showPopover:(UIButton *)sender
{
if ([popoverController isPopoverVisible]) {
[popoverController dismissPopoverAnimated:YES];
} else {
CGRect popRect = CGRectMake(self.button.frame.origin.x,
self.button.frame.origin.y,
self.button.frame.size.width,
self.button.frame.size.height);
[popoverController presentPopoverFromRect:popRect
inView:self.view
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
controller = [[PopoverViewController alloc] initWithNibName:#"PopoverViewController" bundle:nil];
popoverController = [[UIPopoverController alloc] initWithContentViewController:controller];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
NSLog(#"Why am I never called!!!!");
}
- (BOOL)popoverControllerShouldDismissPopover:(UIPopoverController *)popoverController
{
return true;
}
#end
Please tell me where I'm going wrong or how I can detect when a popover is dismissed.
The whole project is here:
https://rapidshare.com/files/3182903825/PopoverDemo.zip
You never set the delegate for your popoverController to self.
_popoverController.delegate = self;
You didn't set the delegate of your popoverController. Add the following code to the end of the viewDidLoad method:
popoverController.delegate = self;
I have a textfield in my form sheet. When the textfield is typed on, I want a popover to appear with a pickerView inside. I've used popOverController before, but in this case I don't have a clue on how to place a pickerView and set it's delegate and datasource to the form sheet view controller, because actually the popover needs an independent view controller.
i tried the following (the app is universal), the popover isn't appearing:
- (IBAction)gradeTextfieldPressed:(UITextField *)sender
{
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
[self.nameTextField resignFirstResponder];
UIViewController *pickerController = [[UIViewController alloc] init];
UIPickerView *pickerView = [[UIPickerView alloc] initWithFrame:CGRectMake(self.gradeTextField.center.x, self.gradeTextField.center.y, 320, 320)];
pickerView.delegate = self;
pickerView.dataSource = self;
[pickerController.view addSubview:pickerView];
UIPopoverController *pickerPopover = [[UIPopoverController alloc] initWithContentViewController:pickerController];
[pickerPopover presentPopoverFromRect:CGRectMake(self.gradeTextField.center.x, self.gradeTextField.center.y, 320, 320) inView:self.view permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
[self.gradeTextField setInputView:pickerView];
if ([self.gradeTextField.text isEqualToString:#""]) {
[self pickerView:pickerView didSelectRow:0 inComponent:0];
}
} else if([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone){
[self.picker setHidden:NO];
//[self pickerView:self.picker didSelectRow:0 inComponent:0];
if ([self.gradeTextField.text isEqualToString:#""]) {
[self pickerView:self.picker didSelectRow:0 inComponent:0];
}
[self.gradeTextField setInputView:self.picker];
[self.nameTextField resignFirstResponder];
[self.creditstextField resignFirstResponder];
[self.chaptersTextField resignFirstResponder];
}
}
One way to do this that I found simple Create a new controller with just the PickerView in it (in this case done with a .xib), and have a simple delegate protocol to let your form sheet controller know when something has been picked.
#protocol MyPickerDelegate <NSObject>
- (void)valuePicked:(NSString*)value
#end
#interface MyPickerController : UIViewController <UIPickerViewDelegate, UIPickerViewDataSource>
{
__weak IBOutlet UIPickerView *_pickerView;
}
#property (nonatomic, weak) id<MyPickerDelegate> delegate;
You can then implement the data source and delegate methods in this controller. Here would be the code for when the item was picked:
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
[_delegate valuePicked:[_myStrings objectAtIndex:row]];
}
And finally, in the calling class (your form sheet controller):
- (void)displayPickerFromRect:(CGRect)rect inView:(UIView*)view
{
if (!_pickerController)
_pickerController = [[MyPickerController alloc] initWithNibName:nil bundle:nil];
if (_pickerController)
{
_speedPickerController.delegate = self;
if (!_pickerPopover)
{
_pickerPopover = [[UIPopoverController alloc] initWithContentViewController:_pickerController];
_pickerPopover.popoverContentSize = _pickerController.view.frame.size;
_pickerPopover.delegate = self;
}
[_pickerPopover presentPopoverFromRect:rect inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
}
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController
{
if (_pickerController)
{
_pickerController = nil;
_pickerPopover = nil;
}
}
- (void)valuePicked:(NSString*)value
{
// do something with the picked value
// dismiss the popover if you want it to go away as soon as a value is picked
}