Mail TabBarItem dismissModalViewControllerAnimated - objective-c

I'm adding TabBarItem (Email) dynamically. When I finish with my email, I'm calling 'dismissModalViewControllerAnimated' but it's simply dismissing mail view. How do I unload the view controller?
in my email view controller I'm doing following:
- (void)viewDidLoad => I would like to unload this view controller
{
[super viewDidLoad];
[self showEMail:nil]; => this display and dismisses email (I'm not calling present.. and dismiss... in a row. I'm using delegate methods to present and dismiss, which is fine.)
[self presentModalViewController:picker animated:YES];
[self dismissModalViewControllerAnimated:YES];
}
after selecting my tab bar item, it's loading view controller(Lets say 'ABC View controller') which is presenting my mail modal controller. mail modal controller is being dismissed properly. but I would like to come back to previously selected tab item after unloading the 'ABC view controller'. is it possible? or am i doing something wrong here?
Thanks in advance
Rama

Rama, your question is hard to understand. maybe you need to use delegation for the view controller you are presenting.
In your code, calling [self presentModalViewController...] and [self dismissModalViewController...] in a row doesn't make sense.
You shall call the [self presentModalViewController...] in the first place, make the presenting view controller as the delegate (picker.delegate = self, for instance)
let the modal view controller do its business including exiting (like user hits cancel, close or whatever), then call the delegation method (something like [delegate didFinish...] or [delegate didCancel...]
now it's the presenting view controller's turn to response to the delegation method calling, you can do the modal view controller dismissing here
Note: many UIKit classes practice this pattern, such as UIAlertView, UIActionSheetView, MPMoviePlayerViewController, etc. You shall check them and make your own

I've solved problem by using ViewWiilAppear method to display mail modal presenter.
and i'm selecting the index of tab bar controller after dismissing mail modal presenter.
self.tabBarController.selectedIndex =0;
This solved my problem.
Cheers

Related

How to dismiss 3 modal view controllers at once?

I have an app that has an initial login screen then when the user wants to sign up, they are presented with a registration form that is three view controllers presented modally. When the user completes the form on the third screen (by pressing a "Done" button), I want the user to be taken back to the initial login screen.
I have tried doing this in the third view controller:
[self dismissViewControllerAnimated:NO completion:nil]
[self.presentingViewController dismissViewControllerAnimated:NO completion:nil]
[self.presentingViewController.presentingViewController dismissViewControllerAnimated:NO completion:nil]
However it only dismissed two of the view controllers and not all 3. Why did this happen?
As other people pointed out, there are more elegant/efficient/easier ways to achieve similar results from the UX perspective: via a navigation controller, or a page view controller, or other container.
Short/quick answer: you need to go one step further in the chain of presenting view controllers, because the dismissal request needs to be sent to the controller that's presenting, and not to the one that's being presented. And you can send the dismiss request to that controller only, it will take care of popping from the stack the child controllers.
UIViewController *ctrl = self.presentingViewController.presentingViewController.presentingViewController;
[ctrl dismissViewControllerAnimated:NO completion:nil]
To explain why, and hopefully help other people better understand the controller presenting logic in iOS, below you can find are more details.
Let's start from Apple documentation on dismissViewControllerAnimated:completion:
Dismisses the view controller that was presented modally by the view controller.
The presenting view controller is responsible for dismissing the view controller it presented. If you call this method on the presented view controller itself, UIKit asks the presenting view controller to handle the dismissal.
Thus [self dismissViewControllerAnimated:NO completion:nil] simply forwarded the request to self.presentingViewController. Which means the first two lines had the same effect (actually the 2nd line did nothing as there was no presented controller after the 1st one executed).
This is why your dismissal of view controllers worked only the top 2 ones. You should've start with self.presentingViewController and go along the chain of presenting view controllers. But this is not very elegant and can cause problems if later on the hierarchy of view controllers changes.
Continuing to read on the documentation, we stumble upon this:
If you present several view controllers in succession, thus building a stack of presented view controllers, calling this method on a view controller lower in the stack dismisses its immediate child view controller and all view controllers above that child on the stack.
So you needn't call dismissViewControllerAnimated:completion: three times, a call on the controller that you want to come back will suffice. At this point, passing a reference to that controller would be more reliable than navigating through the stack of view controllers.
There are some more useful details in the documentation, for example regarding what transitions apply when dismissing multiple controllers at once.
I recommend you go through the whole documentation, not only for this method, but for all methods/classes that you use in your application. You'll likely discover things that will make your life easier.
And if you don't have the time to read all Apple's documentation on UIKit, you can read it when you run into problems, like in this case with dismissViewControllerAnimated:completion: not working as you thought it would.
As a closing note, there are some more subtle issues with your approach, as the actual dismissal takes place in another runloop cycle, as it's possible to generate console warnings and not behave as expected. This is why further actions regarding presenting/dismissing other controllers should be done in the completion block, to give a change to UIKit to finish updating its internal state.
Totally understood. What I will do is embed a navigation controller instead of using modal. I have a case just like you. I have LoginViewController to be the root view controller of the UINavigationController. SignupViewController will be presented by push method. For ResetPasswordViewController, I will use modal because it's supposed to go back to LoginViewController no matter the results. Then, you can dismiss the whole UINavigationController from SignupViewController or LoginViewController.
Second approach will be like, you come up with your own mechanism to reference the presented UIViewController via a shared instance. Then, you can easily dismiss it. Be careful with the memory management. After dismissing it, you should consider whether you need to nil it right away.
I know three ways to dismiss several viewControllers:
Use a chain of completion blocks
~
UIViewController *theVC = self.presentingViewController;
UIViewController *theOtherVC = theVC.presentingViewController;
[self dismissViewControllerAnimated:NO
completion:^
{
[theVC dismissViewControllerAnimated:NO
completion:^
{
[theOtherVC dismissViewControllerAnimated:NO completion:nil];
}];
}];
Use 'viewWillAppear:' method of viewControllers
~
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (self.shouldDismiss)
{
CustomVC *theVC = (id)self.presentingViewController;
theVC.shouldDismiss = YES;
[self dismissViewControllerAnimated:NO completion:nil];
}
}
Pass a reference to LoginVC1 further down the chain.
(This is the best approach so far)
Imagine you have some StandardVC, which presented LoginVC1.
Then, LoginVC1 presented LoginVC2.
Then, LoginVC2 presented LoginVC3.
An easy way of doing what you want would be to call (from inside your LoginVC3.m file)
[myLoginVC1 dismissViewControllerAnimated:YES completion:nil];
In this case your LoginVC1 would lose its strong reference (from StandardVC), which means that both LoginVC2 and LoginVC3 would also be deallocated.
So, all you need to do is let your LoginVC3 know that LoginVC1 exists.
If you don't want to pass a reference of LoginVC1, you can use:
[self.presentingViewController.presentingViewController dismissViewControllerAnimated:NO completion:nil];
However, the above approaches are NOT the correct ways of doing what you want to do.
I would recommend you doing the following:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if (!self.isUserLoggedIn)
{
[UIApplication sharedApplication].keyWindow.rootViewController = self.myLoginVC;
}
return YES;
}
Then, when user finished his login process, you can use
[UIApplication sharedApplication].keyWindow.rootViewController = self.myUsualStartVC;

Load view with the navigation controller?

I have an app where my main view is embedded in a navigation controller. From there, buttons push onto other view controllers. This all works fine. However, one of the view controllers it pushes to updates one of the root's values and presents it again. However this time, it only presents the ViewController without the navigation controller, and of course, pressing the button to go back will end in a crash. Hopefully this picture will help understand my issue. The pressing enter thing isn't really a big deal, I just call this function on return of the keyboard.
Code to go back to the main controller:
-(void)createNewMain:(NSString*)newAddress {
ViewController* newController = [self.storyboard instantiateViewControllerWithIdentifier:#"MainView"];
newController.labelText = newAddress;
newController.connected = self.connected;
[self presentViewController:newController animated:YES completion:nil];
}
The problem is simple, you're presenting the instantiated view controller modally.
Replace
[self presentViewController:newController animated:YES completion:nil];
with
[self.navigationController pushViewController:newController animated:yes];
Also, you can make a segue to do that from the storyboard. When the segue executes, it will create a new instance and will not use the previously created one.
Note: If you really don't need to create a new instance, consider using delegation to exchange information between objects.
You don't really want to "return" to a new instance of a root controller. What you need to do to properly return to the root controller is to pop all the other ones from the navigation controller's stack like this:
[self.navigationController popToRootViewControllerAnimated:YES];
Use Delegation to pass the expected/required message you want from your Pi Controller to your Root View Controller and set it up according to the message. You don't need to create a new instance of your Root View Controller from there. You can always go back to your root view controller from anywhere in the navigation stack by using
[[self navigationController] popToRootViewControllerAnimated:YES];

Should I use the same detail ViewController to work both modally and when pushed?

This seems to be the patten used throughout Apples applications; Creation of a new record is done through a modal View which needs to be saved or canceled to continue, and editing a record is done through a view pushed onto the navigation stack.
It doesn't seem right to be basically duplicating my ViewController for 'add' and 'edit' but there are several differences in how pushed and modal ViewControllers work which complicate things.
How should I be doing this so it can cover both bases?
-
Differences include.
When pushed onto the stack the navBar appears at the top of the View and can be configured to contain the cancel/save buttons. When presented modally this is not the case so to duplicate the interface a toolbar needs to be created separately and close/save buttons added to this instead.
When dismissing a pushed view we send a message to the navigation controller [self.navigationController popViewControllerAnimated:YES];, when dismissing a modal view we send a message to self [self dismissModalViewControllerAnimated:YES];
You could add the UIToolbar in InterfaceBuilder, and then just hide it in viewDidLoad when self.navigationController is not nil.
As for dismissing, you could have something like:
- (void)didCancel {
[self.navigationController popViewControllerAnimated:YES] || [self dismissModalViewControllerAnimated:YES];
}
This will shortcircuit if your viewcontroller is part of a navigationcontrol, and use dismissModalViewControllerAnimated otherwise.
This should work for your cancel button. For your save button, it is useful to call some sort of delegate method such as:
- (void)didSave {
// do your saving juju here
if([self.delegate respondsToSelector:#selector(viewController:didSave:]) {
[self.delegate viewController:self didSave:whatJustGotSaved];
}
[self.navigationController popViewControllerAnimated:YES]; // noop if currently modal
}
In the delegate's implementation then, you can put:
- (void)viewController:(UIViewController*)viewController didSave:(NSObject*)whatJustGotSaved {
// do stuff with parameters
[self.modalViewController dismissModalViewControllerAnimated:YES]; // noop if not modal
}

Passcode ViewController Presentation from Modal View

I'm implementing a Passcode feature in my iPhone app which has a UITabBarController as a root view controller. I have everything working great in most situations, by displaying a modal Passcode ViewController from the tabBarController when the app goes into the background, like so:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
if ([[NSUserDefaults standardUserDefaults] valueForKey:kPasscodeStringKey]) {
PasscodeEntryVC *passcodeView = [[PasscodeEntryVC alloc] init];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:passcodeView];
[tabBarController presentModalViewController:nav animated:NO];
}
}
My problem comes when the app is already displaying a modal view controller when it enters the background. Then, no passcode view appears. What would be the correct way to do this? Instead of just sending the message to the tabBarController to present the view, should I be checking first to see what the current view is, then have that present the passcode? If so, how is this done? Thanks.
First - you are leaking memory because you do not release your passcodeView and navigation controller nav.
Second - you could keep a simple BOOL variable that is updated whenever a modal view is presented or dismissed. If there is a modal view, just call dismissModalViewController:animated: in your applicationDidEnterBackground: method.
You could also check the frontmost view controller with [self.navigationController.topViewController class], but I have found this to be unreliable.
What I usually do is to ensure that any views I have that may present a modal view controller to dismiss the modal view controller whenever it is sent the UIApplicationWillResignActiveNotification notification, while over in my app delegate, I set it up exactly like yours.
One caveat though, is that whenever you dismiss the said modal view controllers, you need to ensure that you dismiss them with animated: set to NO before presenting your passcode view controller.

Stop UIPopover from dismissing automatically

I was wondering if there was a way to stop an iPad popover from dismissing automatically whenever you touch the screen outside the popover? If not, is there some kind of method similar to "popoverDidDismiss" that I could call to tell when the popover was dismissed?
Yes you can. This is right out of the Apple documentation.
When a popover is dismissed due to user taps outside the popover view, the popover automatically notifies its delegate of the action. If you provide a delegate, you can use this object to prevent the dismissal of the popover or perform additional actions in response to the dismissal. The popoverControllerShouldDismissPopover: delegate method lets you control whether the popover should actually be dismissed. If your delegate does not implement the method, or if your implementation returns YES, the controller dismisses the popover and sends a popoverControllerDidDismissPopover: message to the delegate.
Just return NO to the delegate method popoverControllerShouldDismissPopover:
Here is a link for further reading.
Popover Guide
- (BOOL) popoverControllerShouldDismissPopover:(UIPopoverController *)popoverController
{
return NO;
}
That does it for you and you may assign a specific bar button item or something else in your popover to dismiss the popover.
even u can use
self.modallnpopover = yes;
if you want to dismiss it in a particular view
self.modallnpopover = no;
if you dont want to dismiss it