Nothing is editable and no keyboard in MFMailComposeViewController - iphone

I'm launching a MFMailComposeViewController like so:
ShareViewController *shareView = [[ShareViewController alloc] initWithSubject:subject body:body footer:footer];
shareView.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
shareView.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:shareView animated:YES];
The init looks this:
- (id)initWithSubject:(NSString *)subject body:(NSString *)body footer:(NSString *)footer{
[super init];
self.navigationBar.barStyle = UIBarStyleBlack;
self.mailComposeDelegate = self;
[self setSubject:subject];
body = [NSString stringWithFormat:#"%# %#", body, footer];
[self setMessageBody:body isHTML:YES];
return self;
}
The email window appears, and I can move the cursor around the body of the email. But I am not able to bring up a keyboard, or move the cursor to the To:, CC:, or Subject: fields at all. No idea what's going on. This was previously working but I've recently made several UI changes. 'Cancel' dismisses the modal as expected. 'Send' is not enabled since there's no recipient in the To: field.
Any ideas?

Turns out I had forgotten to call [super viewDidAppear] when I overrode viewDidAppear.

Related

Send SMS iPhone

I was trying to send SMS from within my app. I wrote this piece of code but it seems not to work.
No crash, no error log, simply nothing happen (of course I tried to log canSendText and the result is 1).
- (void)viewDidLoad
{
[super viewDidLoad];
messageComposer = [[MFMessageComposeViewController alloc] init];
if ([MFMessageComposeViewController canSendText]) {
[messageComposer setBody:#"Messaggio generato da SMSTest"];
[messageComposer setRecipients:[NSArray arrayWithObject:#"3333333333"]];
[messageComposer setDelegate:self];
[self presentModalViewController:messageComposer animated:YES];
}
}
Can anyone explain me what I'm doing wrong?
The problem is that presentModalViewController does not work in viewDidLoad yet as the view is loaded but might not even be on screen yet. If you put your code in viewWillAppear:animated, this should work.
Edit: As per Saphrosit's comment: viewDidAppear: is an even better place to do this.
I use this successfully:
MFMessageComposeViewController *controller = [[MFMessageComposeViewController alloc] init];
controller.messageComposeDelegate = self;
controller.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentModalViewController:controller animated:YES];
Note that it's messageComposeDelegate, not 'delegate' as you do.
Tim
When i try the code on simulator i get an UIAlert saying text messaging is not available, because simulator canĀ“t send messages. Have you checked that your header file is a delegate of MFMessageComposeViewControllerDelegate ?
YourClassName : UIViewController <MFMessageComposeViewControllerDelegate>
//try this ... it will run ..
MFMessageComposeViewController *controller = [[[MFMessageComposeViewController alloc] init] autorelease];
if([MFMessageComposeViewController canSendText])
{
controller.body = #"Hello from Kartik";
controller.recipients = [NSArray arrayWithObjects:#"12356478", nil];
controller.messageComposeDelegate = self;
[self presentModalViewController:controller animated:YES];
}

Can't set recipients of MFMessageComposeViewController?

I have a method like this:
void sendSMS{
if ([MFMessageComposeViewController canSendText]) {
MFMessageComposeViewController *picker = [[MFMessageComposeViewController alloc] init];
picker.recipients = [NSArray arrayWithObject:#"0933660805"];
[picker setBody:#"Message body"];
picker.messageComposeDelegate = self;
[self.navigationController presentModalViewController:picker animated:YES];
//[picker release];
return;
}
}
Message composer open but recipients and message body are empty (image below). Anybody know how can i fix it :(
Go for this ones and then check may be it will resolve your issue
void sendSMS
{
if ([MFMessageComposeViewController canSendText]) {
MFMessageComposeViewController *picker = [[MFMessageComposeViewController alloc] init];
picker.messageComposeDelegate = self;
NSString *bodyString = nil;
NSMutableArray *toRecipients = [[NSMutableArray alloc]init];
[toRecipients addObject:#"0933660805"];
[picker setRecipients:(NSArray *)toRecipients];
[toRecipients release];
bodyString = [NSString stringWithFormat: #"Message body"];
[picker setBody:bodyString];
[self presentModalViewController:picker animated:YES];
[picker release];
}
Also take a look at this tutorial http://blog.mugunthkumar.com/coding/iphone-tutorial-how-to-send-in-app-sms/
Good Luck!
OK I answered my own question. Now I want no one else to have to go thru this. I was calling this method from just an NSObject. It was a delegate to MFMessageComposeViewControllerDelegate but that made no difference. I had to move this method to my MainViewController, then it worked.
iOS 10.0 is here and this is still a problem for me. So, I have fashioned a workaround.
According to previous comments that initializing the MFMessageComposeViewController in the viewDidLoad() won't solve the problem (which I can attest to), unless the view controller gets presented, it won't be cached. So, the hack here is to make a window, set its root view controller, present a dummy MFMessageComposeViewController instance and immediately dismiss it, somewhere before your actual need (like in viewDidLoad())
Here is a sample code I'm using (Swift 3.0 - Let me know if you were interested in Obj-C counterpart):
let window = UIWindow()
let vc = UIViewController()
window.rootViewController = vc
let messageCompose = MFMessageComposeViewController()
vc.present(messageCompose, animated: false) { [weak messageCompose] in
messageCompose?.dismiss(animated: false, completion: nil)
}
The thing here is that if you present it in the currently active window's view controller chain, it will mess up your UI by showing and hiding the keyboard abruptly (no matter how you try to hide the controller's view and what not), due to the message body selection on present. But adding it to a whole new window which is not in view cycle, it will be correctly initialized and there will be no trace of such transaction on view. Plus, you won't boggle the memory too much this way (because the scope of the controller should be minimal now) and you can initialize your actual MFMessageComposeViewController any time you want and get it much faster now. If your application heavily relies on MFMessageComposeViewController (which I doubt) you can move this to your AppDelegate to be ready anywhere around your app's life cycle.
Cheers,
M.
Try this
- (void)sendSMS
{
if ([MFMessageComposeViewController canSendText]) {
MFMessageComposeViewController *picker = [[MFMessageComposeViewController alloc] init];
picker.messageComposeDelegate = self;
NSString *bodyString = nil;
NSArray *toRecipients = [NSArray arrayWithObject:#"NUMBER HERE"];
[picker setRecipients:toRecipients];
[self presentModalViewController:picker animated:YES];
[picker release];
}
In my case (on iPhone 3g s) the problem was when I called [self.navigationController pushViewController... ], when i tried call [self presentModalViewController ...] it worked, I dont know why, but it is. Try it.
set the MFMessageComposeViewController *picker = [[MFMessageComposeViewController alloc] init];
before if ([MFMessageComposeViewController canSendText]) {...}
Try this.
Try this.
- (void)forwardPromo
{
MFMessageComposeViewController *composeViewController = [[MFMessageComposeViewController alloc] init];
composeViewController.body = #"Message body";
composeViewController.recipients = [NSArray arrayWithObject:#"0933660805"];
composeViewController.messageComposeDelegate = self;
[self presentViewController:composeViewController animated:YES completion:nil];
}
You should have a "nil" at the end of the array:
composeViewController.recipients = [NSArray arrayWithObject:#"0933660805", nil];

MFMessageComposeViewController with Whitespace

I'm using MFMessageComposeViewController for in-app SMS. The problem is that when I have an NSString with whitespace to assign to body, it will remove all whitespaces. Below code demonstrates the issue. Interesting thing is that it works when I try to copy the same text to clipboard and paste into the sms app.
- (void)sendSMS {
MFMessageComposeViewController *controller = [[MFMessageComposeViewController alloc] init];
if([MFMessageComposeViewController canSendText])
{
[controller setBody:#" aaa aaa"];
controller.recipients = [NSArray arrayWithObjects:nil];
controller.messageComposeDelegate = self;
[self presentModalViewController:controller animated:YES];
}
[controller release];
}
//it becomes "aaa aaa" on the SMS message box when controller shows up.
Does anybody have any idea how to solve this problem?
Thanks in advance!
This seems to still be a problem 5/2012. However you can force the whitespace with &nbsp. Not the best option, but it works. The MFMailComposeController will also drop leading whitespace on the body and the same method works.

Exception in iPhone app : Modal transition is already in progress

I have what I believe is a fairly simple application at the moment based on a few tutorials cobbled together. I'm using XCode 3.2.3 in OSX 10.6.4. It started as a standard iPhone "Window Based Application". Using interface builder I have added a Tab Bar Controller using the O'Reilly video tutorial here:
http://broadcast.oreilly.com/2009/06/tab-bars-and-navigation-bars-t.html
In the first Tab I have a standard UIView with two buttons. Both call the same function to display a UIImagePickerController:
-(IBAction) btnPhotoClicked:(id)sender {
UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
imagePicker.delegate = self;
if((UIButton *)sender == btnChoosePhoto)
{
imagePicker.allowsEditing = YES;
imagePicker.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
} else {
imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
}
[self presentModalViewController:imagePicker animated:YES];
[imagePicker release];
}
I am running the code inside an emulator so only ever click the button called Choose Photo. When the dialogue is released with a photo chosen this function runs:
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
NSURL *mediaUrl;
mediaUrl = (NSURL *)[info valueForKey:UIImagePickerControllerMediaURL];
if (mediaUrl == nil)
{
imagePuzzle = (UIImage *) [info valueForKey:UIImagePickerControllerEditedImage];
if(imagePuzzle == nil)
{
//--- Original Image was selected ---
imagePuzzle = (UIImage *) [info valueForKey:UIImagePickerControllerOriginalImage];
}
else {
//--- Get the edited image ---
//--- If it was successful the above valueForKey:UIImagePickerControllerEditedImage
//--- would have assigned it already.
}
}
else {
//--- Muppet selected a video
}
// Animate the picker window going away
[picker dismissModalViewControllerAnimated:YES];
ImageViewController *imageViewController = [[ImageViewController alloc] init];
imageViewController.delegate = self;
[self presentModalViewController:imageViewController animated:YES];
[imageViewController release];
}
This is where my problem lies. I've tried many different hacks and iterations but the above code is the simplest to present the problem. When the imageViewController is displayed as a modal dialogue the following exception is thrown:
2010-07-09 15:29:29.667 Golovomka[15183:207] *** Terminating app due to uncaught
exception 'NSInternalInconsistencyException', reason: 'Attempting to begin a modal
transition from <NewViewController: 0x5915f80> to <ImageViewController: 0x594a350>
while a transition is already in progress. Wait for viewDidAppear/viewDidDisappear
to know the current transition has completed'
How do I cure this? I have tried delays and other tricks but do not really understand how I'm supposed to use viewDidAppear or viewDidDisappear to help me. Also of note is that a very basic application with one view loading the picker then displaying another view with the image in does not produce the error. Any help gratefully received.
To address the specific issue described here, you could add the viewDidAppear method in your class:
-(void)viewDidAppear:(BOOL)animated
{
if (/*just visited ImagePicker*/)
{
ImageViewController *imageViewController = [[ImageViewController alloc] init];
imageViewController.delegate = self;
[self presentModalViewController:imageViewController animated:YES];
[imageViewController release];
}
}
Remove those lines from below your call:
[picker dismissModalViewControllerAnimated:YES];
So, whenever your class self appears (is displayed), it will call viewDidAppear... Since this most likely isn't really what you want all the time, you could add some variables to set/clear that defines whether or not to immediately present the imageViewController when self is displayed. Something like "If coming from image picker, show the imageViewController, otherwise do nothing".
That said, imho, pushing modal views is should generally be done in response to a user action and I would maybe rethink the user experience here - e.g. add a subview instead of pushing a modal view which you could do where your currently have the code - but if you're just playing around with some tutorials that should solve the NSInternalInconsistencyException. :) Cheers!
In iOS 5.0 and above you can use
[self dismissViewControllerAnimated:YES completion:^{
//present another modal view controller here
}];
I ran into this issue quite a few times. I recently started using this simple fix:
When I am going to present a new modal view controller immediately after dismissing another modal view controller, I simply dismiss the first one with argument NO in dismissModalViewControllerAnimated:.
Since the second view is presented with an animation, you hardly notice that the first one goes away fast. And you never get the transitions conflict.
I was having the same problem when i wanted to present an MFMailComposeViewController immediately after dismissing the UIImagePickerController. Heres what i did:
I removed the [imagePicker release]; statement from where i was presenting the image picker and put it in didFinishPickingMedia callback.
I used [self performSelector:#selector(presentMailComposer:) withObject:image afterDelay:1.0f];
Here's my code:
Displaying Image Picker
if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) {
NSArray *media = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypePhotoLibrary];
if ([media containsObject:(NSString*)kUTTypeImage] == YES) {
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
[picker setMediaTypes:[NSArray arrayWithObject:(NSString *)kUTTypeImage]];
picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
picker.delegate = self;
[self presentModalViewController:picker animated:YES];
//[picker release];
}
}
else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Unavailable!"
message:#"Could not open the Photo Library."
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
[alert release];
}
Image Picker Delegate Callback - didFinishPickingMedia
NSString *mediaType = [info valueForKey:UIImagePickerControllerMediaType];
if([mediaType isEqualToString:(NSString*)kUTTypeImage]) {
UIImage *photoTaken = [info objectForKey:#"UIImagePickerControllerOriginalImage"];
//Save Photo to library only if it wasnt already saved i.e. its just been taken
if (picker.sourceType == UIImagePickerControllerSourceTypeCamera) {
UIImageWriteToSavedPhotosAlbum(photoTaken, self, #selector(image:didFinishSavingWithError:contextInfo:), nil);
}
//Pull up MFMailComposeView Controller
[self performSelector:#selector(composeMailWithPhoto:) withObject:photoTaken afterDelay:1.0f];
}
[picker dismissModalViewControllerAnimated:YES];
[picker release];
Display Mail Composer View
if ([MFMailComposeViewController canSendMail]) {
MFMailComposeViewController *mailPicker = [[MFMailComposeViewController alloc] init];
mailPicker.mailComposeDelegate = self;
// Fill out the email fields and Attach photograph to mail
static NSString *imageType = #"image/jpeg";
NSString *imageName = [NSString stringWithString:#"MyCoffeeCup.jpg"];
NSData *imageData = UIImageJPEGRepresentation(image, 1.0);
[mailPicker addAttachmentData:imageData mimeType:imageType fileName:imageName];
[mailPicker setToRecipients:[NSArray arrayWithObject:#"hello#xische.com"]];
[self presentModalViewController:mailPicker animated:YES];
//[self.navigationController pushViewController:mailPicker animated:YES];
[mailPicker release];
}
else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Unavailable!"
message:#"This device cannot send emails."
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
[alert release];
}

how to get around lack of 'add' button in ABPeoplePickerNavigationController?

My app needs to associate instances of a custom class with contact records in the iPhone's AddressBook. Everything's all well and good when I present the ABPeoplePickerNavigationController and allow the user to pick an existing contact. Problem is there's no obvious way to allow a user to easily ADD a contact record if the one they're looking for doesn't already exist in their AddressBook.
How are people getting from ABPeoplePickerNavigationController to ABNewPersonViewController in a way that's easy & intuitive for the user?
You can create a UIBarButton and add it to the UINavigationBar of the ABPeoplePickerNavigationController like so.
peoplePicker.topViewController.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(addPerson:)];
-(IBAction)addPerson:(id)sender{
ABNewPersonViewController *view = [[ABNewPersonViewController alloc] init];
view.newPersonViewDelegate = self;
UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:view];
[self.picker presentModalViewController:nc animated:YES];
}
The issue that i came up against was that the ABPeoplePickerNavigationController has a cancel button placed in the rightBarButtonItem slot and I had to update the navigation bar on the
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated{
I have documented the entire process on my blog with a worked example that should allow you to create a contacts style application similar to that on the iPhone. Hope this helps.
I found Scott Sherwood's approach along with the demo he posted on his site to be very helpful. As one of the commenters on his blog mentioned though, there is a problem with the Cancel button in Edit mode.
I just proposed a fix to Scott's demo, along with a different approach for the Person View Controller at:
http://finalize.com/2013/05/12/using-and-customizing-the-address-book-ui/
My suggestion for the Person View Controller was to put it up manually in the protocol method peoplePickerNavigationController:shouldContinueAfterSelectingPerson: for the ABPeoplePickerNavigationControllerDelegate.
// Displays the information of a selected person
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person
{
ABPersonViewController *view = [[ABPersonViewController alloc] init];
view.personViewDelegate = self;
view.displayedPerson = person; // Assume person is already defined.
view.allowsEditing = YES;
view.allowsActions = YES;
[peoplePicker pushViewController:view animated:YES];
return NO;
}
The only issue here is that the People Picker table view of names is not refreshed automatically after an edit. This can be fixed with the use of an Address Book callback. I show how this can be done in the GitHub project I posted at:
https://github.com/scottcarter/AddressBookPeoplePicker.git
it appears that it is not possible to add a new contact directly from the ABPeoplePickerNavigationController. Therefore, when the user clicks an add button, I am presenting an UIActionSheet with two buttons:
- (void) addContact{
contactMenu = [[UIActionSheet alloc]
initWithTitle: nil
delegate:self
cancelButtonTitle:#"Cancel"
destructiveButtonTitle: nil
otherButtonTitles:#"Select a contact", #"Add a new contact", NULL];
[contactMenu showInView:self.view];
}
Here is the associated delegate method:
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
if(buttonIndex == 0){
// select an existing contact
ABPeoplePickerNavigationController *peoplePicker = [[ABPeoplePickerNavigationController alloc] init];
peoplePicker.peoplePickerDelegate = self;
[self presentModalViewController:peoplePicker animated:YES];
}
if(buttonIndex == 1){
// add a new contact
ABNewPersonViewController *newPersonViewController = [[ABNewPersonViewController alloc] init];
newPersonViewController.newPersonViewDelegate = self;
UINavigationController *personNavController = [[UINavigationController alloc] initWithRootViewController:newPersonViewController];
[self presentModalViewController:personNavController animated:YES];
[personNavController release];
[newPersonViewController release];
}
if(buttonIndex == 2){
// cancel the operation
[actionSheet dismissWithClickedButtonIndex:2 animated:YES];
}
}