I'm trying to track down the source of a bug in a cordova/phonegap plugin I wrote for creating email messages in app using a MFMailComposeViewController instance.
Everyone works fine the first time you present the the composer view. The user can dismiss the mail composer by sending the message or canceling. However, call presentViewController again renders the Cancel and Send buttons in the composer to become useless. My delegate for didFinishWithResult is never calling when pressing the inoperable buttons with the second view of the controller.
Below is simplified repro of what I'm seeing (the simple storyboard has a single view containing a single UIButton wired to my (IBAction)sendMail). What am I doing wrong in obj-c here? Shouldn't I be able to show a controller, dismiss it, and show it again?
ViewController.h:
#import <UIKit/UIKit.h>
#import <MessageUI/MessageUI.h>
#interface ViewController : UIViewController
#end
ViewController.m:
#import "ViewController.h"
#interface ViewController () <MFMailComposeViewControllerDelegate>
#property (nonatomic, weak) IBOutlet UIButton *mailButton;
#property(nonatomic, strong) MFMailComposeViewController* picker;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.picker = [[MFMailComposeViewController alloc] init];
self.picker.mailComposeDelegate = self;
}
- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error
{
[self dismissViewControllerAnimated:YES completion:NULL];
}
- (IBAction)sendMail
{
[self presentViewController:self.picker animated:YES completion:NULL];
}
#end
The reason for the behavior you are experiencing is the MFMailComposeViewController nils it's delegate when dismissed (maybe in -viewDidDisappear:).
- (void)viewDidLoad
{
[super viewDidLoad];
self.picker = [[MFMailComposeViewController alloc] init];
self.picker.mailComposeDelegate = self;
}
- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error
{
// Put a break point here **#breakpoint1**
[self dismissViewControllerAnimated:YES completion:NULL];
}
- (IBAction)sendMail
{
// Put a break point here **#breakpoint2**
[self presentViewController:self.picker animated:YES completion:NULL];
}
Place breakpoints at shown in the code comment above, run, and follow me as we step through your code.
Tap the interface button that calls your IBAction; execution halts at #breakpoint2
In the console type po self.picker
You'll see the mail compose VC instance is allocated
In the console type po self and then po self.picker.delegate
You'll see these both print the same object (the instance of your view controller)
Resume running, and tap the dismiss button on the mail compose view; execution halts at #breakpoint1
If you want to, inspect local and instance variables in console and then resume running
Tap the interface button that calls your IBAction (this is the second time); execution halts at #breakpoint2
In the console typ po self.picker.delegate
nil is printed to console
This delegate nil'ing behavior isn't documented in either Apple's MFMailComposeViewController class reference or the classes header. It's probably worth filing a bug report requesting clarification and better documentation. Because it's undocumented, the behavior may change in future releases. For that reason, the suggestions to create and destroy the VC as needed certainly seem like good common sense.
This bit me once before. It's caused by the composer being deallocated after it's done being dismissed. To solve this I would place the composer's creation either in viewDidAppear:, or in sendMail as Fahim suggested.
Additionally, you may want to consider wrapping these two lines in [MFMailComposeViewController canSendMail];
I would say take below lines to sendMail... it would work.
self.picker = [[MFMailComposeViewController alloc] init];
self.picker.mailComposeDelegate = self;
You will have as below.
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (IBAction)sendMail
{
self.picker = [[MFMailComposeViewController alloc] init];
self.picker.mailComposeDelegate = self;
[self presentViewController:self.picker animated:YES completion:NULL];
}
#end
This is working with me...
Related
Okay it seems like I have made some mistakes and I did not get it with pointer and initializations by now...
Here is the problem :
I have a UIViewController for a registration process called : RegisterViewController
It calls a method in its ViewDidLoad :
[self performSelector:#selector(activateUsernamePopover) withObject:nil afterDelay:0.1];
This method looks like this :
- (void) activateUsernamePopover {
PopoverViewController *popcontroller = [[PopoverViewController alloc] init];
popcontroller.title = nil;
[popcontroller setPopoverText:#"Test"];
FPPopoverController *popover = [[FPPopoverController alloc] initWithViewController:popcontroller];
popover.arrowDirection = FPPopoverArrowDirectionUp;
popover.border = NO;
popover.tint = MgoGreyTint;
[popover setShadowsHidden:true];
[popover presentPopoverFromView:_usernameInput]; }
This will made a Popover visible. This works great.
But I Do have a few more TextFields where I want to show a Popover with a different text.
So I made a method in the PopoverViewController called setPopoverText :
- (void)setPopoverText:(NSString *)text {
[_popoverLabel setText:text];
[_popoverLabel setNeedsDisplay]; }
I call it in my activateUsernamePopover method :
[popcontroller setPopoverText:#"Test"];
And there is the problem.
I can log the text in the PopoverViewControllers method setPopoverText its fine.
But it did not change the text. I logged the _popoverLabel like this :
NSLog(#"%#",_popoverLabel);
and its (null).
I know there is some issue with the pointer or the instance of PopoverViewController I am working with, but objective c is not that clear to me yet.
Anyone got some answers for me ?
How can I change the Text of that UILabel ?
I also could imagine giving the Text to the Controller while instancing it.
Something like that :
PopoverViewController *popcontroller = [[PopoverViewController alloc] initWithPopoverText:#"Test"];
But I don´t know how. I don´t need to change the Text while the popover is visible. It will be released when the user taps in the TextField or elsewhere.
Thanks so far.
Since the UILabel is not created yet when you call init method. the way to do it is to keep text in the NSString property.
In you PopoverViewController, create the init method like this
#interface ViewController : UIViewController
- (id)initWithPopoverText:(NSString *)text;
#end
In the implementation file, keep hold of the text in the property and on viewDidLoad, you could set the text to the label.
#interface PopoverViewController ()
#property (nonatomic) NSString *popoverText;
#end
#implement PopoverViewController
- (id)initWithPopoverText:(NSString *)text {
self = [super init];
if (self) {
_popoverText = text;
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
//set label.text here
self.popoverLabel.text = self.popoverText;
}
#end
I have a question about CvPhotoCamera (OpenCV) in the iOS App.
I have a myViewController1: in this viewController I push a mySecondView controller.
In this second View Controller I use CvPhotoCamera:
I have a UIImageViewController.
In viewDidLoad I have this code:
- (void)viewDidLoad
{
[super viewDidLoad];
_photoCamera = [[CvPhotoCamera alloc] initWithParentView:imageView];
_photoCamera.defaultAVCaptureSessionPreset = AVCaptureSessionPresetPhoto;
_photoCamera.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationPortrait;
[_photoCamera setDefaultAVCaptureDevicePosition:AVCaptureDevicePositionBack];
_photoCamera.delegate = self;
[_photoCamera start];
}
in viewDidDisappear I have this code:
- (void) viewDidDisappear:(BOOL)animated
{
[_photoCamera stop];
}
I use CvPhotoCamera to take a photo from camera using the method:
- (IBAction)actionStart:(id)sender;
{
[_photoCamera takePicture];
}
The my problem is this:
When I push this second ViewController and I tap on back button on Nav bar I have some memoryWarning and the app crashes ..always!!!
I used also the profile Xcode tool to manage memory allocation or memory leak but I do not see anything strange.
Is correct this use of CvPhotoCamera obj?
I´m trying to integrate ShareKit in my ios game.
Everything is working fine and the actionsheet is shown and I can interact with it but I´m not able to return the focus to my app when the sharekit action has finished (by closing the actionsheet or finishing any action).
I have tried in several ways but any has worked for me. What´s happening?
I´m not an expert programmer so I expect I´m missing something.
I´m
This is my .h
#import <UIKit/UIKit.h>
#import "SHK.h"
#import "SHKConfiguration.h"
#interface SocialWrapper: UIViewController{
}
- (id) init;
- (void) open;
- (void) dealloc;
#end
and .m
#import "SocializeWrapper.h"
#implementation SocialWrapper
- (id) init {
self=[super init];
DefaultSHKConfigurator *configurator = [[DefaultSHKConfigurator alloc] init];
[SHKConfiguration sharedInstanceWithConfigurator:configurator];
[SHK flushOfflineQueue];
return self;
}
- (void) open
{
NSString *someText = #"Hello Earth!";
SHKItem *item = [SHKItem text:someText];
// Get the ShareKit action sheet
SHKActionSheet *actionSheet = [SHKActionSheet actionSheetForItem:item];
UIWindow *window = [UIApplication sharedApplication].keyWindow;
[window addSubview:self.view];
[SHK setRootViewController:self];
[actionSheet showInView:self.view];
}
- (void) dealloc {
NSLog(#"SHK dealloc");
[self release];
[super dealloc];
}
#end
I´m calling it by using this wrapper
#import "SocializeWrapper.h"
SocialWrapper *socialize;
void SHKinit(void) {
NSLog(#"SHK Init");
socialize = [[SocialWrapper alloc] init];
}
void SHKopenWeb(void){
NSLog(#"SHK Open actionsheet");
[socialize open];
}
I´m working with ios 5, xcode 4.3.2 and the last sharekit version from the git.
I think I have to dissmiss my SocialWrapper once the actionsheet is closed but I don´t know how to capture that event, or even if this is correct. I´m stucked.
any help will be greatly appreciated.
UPDATE
As comment adviced, now the controller is on a category, using the actionsheet delegate, the focus can be regained when clicking the cancel´s actionsheet button. The problem still persists when an action is finished or cancelled. Don´t know how to capture that event.
This is my category code:
#import "SocialWrapper.h"
#implementation UIViewController (SocialController)
-(void) loadconfig
{
DefaultSHKConfigurator *configurator = [[DefaultSHKConfigurator alloc] init];
[SHKConfiguration sharedInstanceWithConfigurator:configurator];
[SHK flushOfflineQueue];
}
- (void) open
{
NSLog(#"Opening social button");
NSString *someText = #"Monkey Armada rules!";
SHKItem *item = [SHKItem text:someText];
// Get the ShareKit action sheet
SHKActionSheet *actionSheet = [SHKActionSheet actionSheetForItem:item];
UIWindow *window = [UIApplication sharedApplication].keyWindow;
[window addSubview:self.view];
[actionSheet setDelegate:self];
[SHK setRootViewController:self];
[actionSheet showInView:self.view];
}
- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex
{
NSLog(#"SHK actionsheet dissmiss with button %d", buttonIndex);
if(buttonIndex == 4)
{
NSLog(#"SHK close actionsheet");
[self dismissViewControllerAnimated:YES completion:nil];
[self.view removeFromSuperview];
}
}
#end
Well since SHKActionSheet is a subclass of UIActionSheet you can set the delegate of that class to self to know when the dismissal happens.
Also, [self release]; in dealloc is complete misunderstanding of what release does, if you're in dealloc then releasing self won't do anything !
Learn the memory management rules.
I should also warn you that [window addSubview:self.view] is deprecated, you should not do that at all. In fact, I don't see a reason to wrap share kit stuff each view controller should be able to write that code easily. At worse you could put that code in a category on UIViewController if you don't want to rewrite the code every time.
The scenario is that I have more than one view that wants to invoke the Address Book. So as not to duplicate the code of the delegate in each view I have located the code in the App Delegate's header and .m file, but using an "#interface AddressBookDelegate" and "#implementation AddressBookDelegate" at the bottom of the 2 respective App Delegate fiies-
#interface AddressBookDelegate : UIViewController <ABPeoplePickerNavigationControllerDelegate> {
AddressBookDelegate *addressBookDelegate;
}
#property (nonatomic, retain) AddressBookDelegate *addressBookDelegate;
#end
and
#implementation AddressBookDelegate
#synthesize addressBookDelegate;
- (void)peoplePickerNavigationControllerDidCancel: (ABPeoplePickerNavigationController *)peoplePicker
{
[self dismissModalViewControllerAnimated:YES];
}
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person
{
[super dismissModalViewControllerAnimated:YES];
...get stuff from the Address Book...
return NO;
}
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person
property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier
{
return NO;
}
Then in my views I have the following code:
addressBookDelegate = (AddressBookDelegate *) [[UIApplication sharedApplication] delegate];
ABPeoplePickerNavigationController *abPicker = [[ABPeoplePickerNavigationController alloc]init];
abPicker.peoplePickerDelegate = self.addressBookDelegate;
[self presentModalViewController:abPicker animated:YES];
[abPicker release];
The Address Book displays fine in all views. But when I take any user action that would invoke a delegate, like the Address Book's Cancel button, I crash-
-[MyprogAppDelegate peoplePickerNavigationControllerDidCancel:]: unrecognized selector sent to instance
It compiles clean, no Warnings.
How do I wire-up the peoplePickerDelegate to connect to the Address Delegate code when it is not physically in the same file as the view itself ? Thx.
ADDED NOTE: when I use the debugger and stop on the line
abPicker.peoplePickerDelegate = addressBookDelegate;
in the view code, I see that the address for the addressBookDelegate is stated to be the address of the MyprogAppDelegate, not AddressBookDelegate as I might have expected. That makes me think the displacement to the address book delegate code is off within the App Delegate file.
If the AddressBookDelegate Cancel Delegate code were say 1000 bytes into the AddressBookDelegate, my app is actually "entering" the code 1000 bytes into MyprogAppDelegate, and so crashes. So somehow I am not setting up the addressing of the AddressBookDelegate correctly. That's my take on it anyway...
Your code assumes that your appdelegate (MyprogAppDelegate) implements method peoplePickerNavigationControllerDidCancel.
So, your code in MyprogAppDelegate should be something like this:
#implementation MyprogAppDelegate
#synthesize ...;
#pragma mark -
#pragma mark Application lifecycle
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
return YES;
}
- (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker{
}
EDIT Okay, the entire first answer has been tossed out. This is, with some warning, still a bit of a shot in a dark, but I think it's going to be closer to helpful. Some of the ideas do carry over though.
You very probably don't need a separate class to act as your ABPeoplePickerNavigationControllerDelegate. In all likelihood, it should be the same class that has your code at the bottom (that calls presentModalViewController:animated:. Since I don't know what controller that was, I'm going to just call it MyViewController for reference. The reason you want that view controller to be the delegate is because, in your delegate methods, you need to be able to dismiss the modal view controller that has the address book.
You definitely don't want the your program's UIApplicationDelegate to be the ABPeoplePickerNavigationControllerDelegate. As you said yourself, peoplePickerDelegate has to be a UIViewController.
So, to MyViewController. First, the interface:
/* MyViewController.h */
#interface MyViewController : UIViewController<ABPeoplePickerNavigationControllerDelegate>
...
#end
Your controller might inherit from a descendant of UIViewController (like a table view controller or something like that) - that shouldn't change, the only thing that should change is adding the ABPeoplePickerNavigationControllerDelegate to the list of implemented protocols.
Now, to implement the functionality:
/* MyViewController.m */
#implementation MyViewController
...
- (void) whateverMethodIsDisplayingTheAddressBook
{
ABPeoplePickerNavigationController *abPicker = [[ABPeoplePickerNavigationController alloc]init];
abPicker.peoplePickerDelegate = self; // This view controller is the delegate
[self presentModalViewController:abPicker animated:YES];
[abPicker release];
}
...
- (void)peoplePickerNavigationControllerDidCancel: (ABPeoplePickerNavigationController *)peoplePicker
{
[self dismissModalViewControllerAnimated:YES];
}
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person
{
[super dismissModalViewControllerAnimated:YES];
...get stuff from the Address Book...
return NO;
}
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person
property:(ABPropertyID)property
identifier:(ABMultiValueIdentifier)identifier
{
return NO;
}
#end
In the end I was not able to make any of the above suggestions perform as hoped for. I had to cut time and move on so I duplicated the code in each view. I will revisit this another time, as I am sure it can be done in a more object based way than I ended it doing it.
So I have a subclass of UITableViewController that loads some data from the internet and uses MBProgressHUD during the loading process. I use the standard MBProgressHUD initialization.
HUD = [[MBProgressHUD alloc] initWithView:self.view];
[self.view addSubview:HUD];
HUD.delegate = self;
HUD.labelText = #"Loading";
[HUD show:YES];
This is the result:
.
Is there any way to resolve this issue, or should I just abandon MBProgressHUD?
Thanks!
My solution was pretty simple. Instead of using self's view, I used self's navigationController's view.
HUD = [[MBProgressHUD alloc] initWithView:self.navigationController.view];
[self.navigationController.view addSubview:HUD];
This should work for the OP because his picture shows he's using a UINavigationController. If you don't have a UINavigationController, you might add another view on top of your UITableView, and add the HUD to that. You'll have to write a little extra code to hide/show this extra view.
An unfortunate thing with this simple solution (not counting my idea adding another view mentioned above) means the user can't use the navigation controls while the HUD is showing. For my app, it's not a problem. But if you have a long running operation and the user might want to press Cancel, this will not be a good solution.
It's probably because self.view is a UITableView, which may dynamically add/remove subviews including the headers, which could end up on top of the HUD after you add it as a subview. You should either add the HUD directly to the window, or (for a little more work but perhaps a better result) you could implement a UIViewController subclass which has a plain view containing both the table view and the HUD view. That way you could put the HUD completely on top of the table view.
My solution was:
self.appDelegate = (kmAppDelegate *)[[UIApplication sharedApplication] delegate];
.
.
_progressHUD = [[MBProgressHUD alloc] initWithView:self.appDelegate.window];
.
[self.appDelegate.window addSubview:_progressHUD];
Works like a charm for all scenarios involving the UITableViewController. I hope this helps someone else. Happy Programming :)
Create a category on UITableView that will take your MBProgressHUD and bring it to the front, by doing so it will always appear "on top" and let the user use other controls in your app like a back button if the action is taking to long (for example)
#import "UITableView+MBProgressView.h"
#implementation UITableView (MBProgressView)
- (void)didAddSubview:(UIView *)subview{
for (UIView *view in self.subviews){
if([view isKindOfClass:[MBProgressHUD class]]){
[self bringSubviewToFront:view];
break;
}
}
}
#end
A simple fix would be to give the z-index of the HUD view a large value, ensuring it is placed in front of all the other subviews.
Check out this answer for information on how to edit a UIView's z-index: https://stackoverflow.com/a/4631895/1766720.
I've stepped into a similar problem a few minutes ago and was able to solve it after being pointed to the right direction in a different (and IMHO more elegant) way:
Add the following line at the beginning of your UITableViewController subclass implementation:
#synthesize tableView;
Add the following code to the beginning of your init method of your UITableViewController subclass, like initWithNibName:bundle: (the beginning of viewDidLoad might work as well, although I recommend an init method):
if (!tableView &&
[self.view isKindOfClass:[UITableView class]]) {
tableView = (UITableView *)self.view;
}
self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
self.tableView.frame = self.view.bounds;
self.tableView.contentInset = UIEdgeInsetsMake(0.0, 0.0, 0.0, 0.0);
[self.view addSubview:self.tableView];
Then you don't need to change your code you posted in your question any more. What the above code does is basically seperating the self.tableView from self.view (which was a reference to the same object as self.tableView before, but now is a UIView containing the table view as one might expect).
I've Just solved that issue manually , it has been 2 years since Chris Ballinger asked but maybe someone get used of what is going on here.
In UITableViewController i execute an HTTP method in viewDidLoad , which is running in background so the table view is loaded while the progress is shown causing that miss.
i added a false flag which is changed to yes in viewDidLoad, And in viewDidAppear something like that can solve that problem.
-(void) viewDidAppear:(BOOL)animated{
if (flag) {
[self requestSomeData];
}
flag = YES;
[super viewDidAppear:animated];
}
I had the same problem and decided to solve this by changing my UITableViewController to a plain UIViewController that has a UITableView as a subview (similar to what jtbandes proposed as an alternative approach in his accepted answer). The advantage of this solution is that the UI of the navigation controller isn't blocked, i.e. users can simply leave the ViewController in case they don't want to waiting any longer for your timely operation to finish.
You need to do the following changes:
Header file:
#interface YourViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
- (id)initWithStyle:(UITableViewStyle)style;
#end
Implementation file:
#interface YourViewController ()
#property (nonatomic, retain) UITableView *tableView;
#property (nonatomic, retain) MBProgressHUD *hud;
#end
#implementation YourViewController
#pragma mark -
#pragma mark Initialization & Memory Management
- (id)initWithStyle:(UITableViewStyle)style;
{
self = [super init];
if (self) {
// create and configure the table view
_tableView = [[UITableView alloc] initWithFrame:CGRectNull style:style];
_tableView.delegate = self;
_tableView.dataSource = self;
}
return self;
}
- (void)dealloc
{
self.tableView = nil;
self.hud = nil;
[super dealloc];
}
#pragma mark -
#pragma mark View lifecycle
- (void)loadView {
CGRect frame = [self boundsFittingAvailableScreenSpace];
self.view = [[[UIView alloc] initWithFrame:frame] autorelease];
// add UI elements
self.tableView.frame = self.view.bounds;
[self.view addSubview:self.tableView];
}
- (void)viewWillDisappear:(BOOL)animated
{
// optionally
[self cancelWhateverYouWereWaitingFor];
[self.hud hide:animated];
}
The method -(CGRect)boundsFittingAvailableScreenSpace is part of my UIViewController+FittingBounds category. You can find its implementation here: https://gist.github.com/Tafkadasoh/5206130.
In .h
#import "AppDelegate.h"
#interface ViewController : UITableViewController
{
MBProgressHUD *progressHUD;
ASAppDelegate *appDelegate;
}
In .m
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
appDelegate = (ASAppDelegate *) [UIApplication sharedApplication].delegate;
progressHUD = [MBProgressHUD showHUDAddedTo:appDelegate.window animated:YES];
progressHUD.labelText = #"Syncing To Sever";
[appDelegate.window addSubview:progressHUD];
This should work.
[MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
And to remove you can try
[MBProgressHUD hideHUDForView:self.navigationController.view animated:YES];