NSNotificationCenter for presenting multiple modals? - objective-c

I have an app delegate, whose default view should be preceeded by a modal view controller, and sometimes by two modal view controllers. So in the app delegate's didFinishLaunchingWithOptions, I'm checking if there is need for, and in that case displays, the first modal view controller.
Upon dismissing the first modal view controller (using [self dismissModalViewControllerAnimated:YES];), I may want to display the second modal view controller. This is known by the app delegate as well.
So my solution was to use NSNotificationCenter to tell the app delegate that the first modal view controller now have been dismissed. When that happens, the second modal view controller can be displayed by the app delegate, if it is needed.
It works fine, but is there a cleaner solution? I think NSNotificationCenter is really ugly stuff.
Note on displaying multiple modal view controllers at once
I did try to display the first AND the second modal view controller inside of didFinishLaunchingWithOptions, but I never got it working. Here's what I tried:
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[window setRootViewController:tabBarController];
[self.window makeKeyAndVisible];
[tabBarController presentModalViewController:pinViewController animated:NO];
if([self needsActivation]) {
[tabBarController presentModalViewController:activationViewController
animated:YES];
}
}
UPDATE: The above code works with the following correction:
if([self needsActivation]) {
[pinViewController presentModalViewController:activationViewController
animated:YES];
}

In this particular case, there was no need for NSNotificationCenter, as I thought. I had tried to display multiple modal view controllers, but I'd made an error.
When displaying the modal view controller B from the modal view controller A, it works fine. I had tried presenting modal view controller A and B from a parent view controller.
So when presenting modal view controllers in a hierarchy instead, there is no need for NSNotificationCenter. The view controllers are dismissing themselves, animations works and I'm a step further towards bending the UIKit to my will.
I've edited the code in my question, which now works fine.

Related

Unbalanced calls in iOS 8

I'm getting the following warning when I present a modal view controller on a navigation controller in iOS 8 at launch. It works fine on iOS 7.
Unbalanced calls to begin/end appearance transitions for UINavigationController.
Here's what I'm doing.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
...
[self.window makeKeyAndVisible];
...
[self.navigationController presentViewController:self.modalViewController animated:NO completion:nil];
}
In iOS 8 I can see the navigation controller for a split second before the modal view is presented. In iOS 7 I see the modal view controller immediately without the warning.
How to I get the same behavior in iOS 8?
In order to get rid of the warning, you need to move the code to your first view controllers viewDidAppear. In order to make it look nicer, you might consider doing some tricks. I had the same problem, and I solved it by showing a full screen image of your modal view controller on your first view controller, and hide this image after the modal view controller is presented, e.g. using the method performSelector:withObject:afterDelay

Does presentModalViewController: add the view controller to the stack?

I have a main navigation controller with a root view controller. In the root view controller, on the push of a button I present second view controller like this:
SecondVC *secondVC = [[SecondVC alloc] initWithNibName:#"SecondVC" bundle:nil];
[self.navigationController presentModalViewController:secondVC animated:YES];
In the second view controller, on the push of an other button, I want to present a third view controller (this time from a Storyboard):
ThirdVC *thirdVC = [[UIStoryboard storyboardWithName:#"Settings" bundle:nil] instantiateInitialViewController];
[self.navigationController presentModalViewController:thirdVC animated:YES];
However this doesn't do anything. I debugged and it turned out, that self.navigationController is nil.
Shouldn't it be the main navigation controller? Or doesn't presentModalViewController: add the view controller to the stack? Do I always have to put a view controller in a navigation controller before presenting id modally?
The new view controller SecondVC is being presented modally, and it's not added to the view controller stack of the navigationController. You need to create a new UINavigationController, and put SecondVC inside the navController before presenting it modally.
You'll need to add something like:
UINavigationController *navControl = [[UINavigationController alloc] initWithRootViewController:secondVC];
[self addChildViewController:navController];
[self.navigationController presentModalViewController:secondVC animated:<#(BOOL)#>]
your view controller while being presented is not inside a navigation controller. And will not have access to the presenting controllers navigation controller.
Furthermore if you push or pop stack items on the navigation controller beneath the modal view controller you will likely not notice anything.
If you want to put the controller in the stack you can alternatively show the view controller yourself.
[self.view addSubView:myViewController.view]
myViewController.view.frame = self.view.bounds;
and to dismiss the view controller you would simply remove it from its superview.
the drawback here is that some of the did and will appear methods are not called on the view controller. Therefore you may want to call them yourself.
But the principal is much the same. And you can easily simulate the presenting animation with the animation system.
Give it a starting point below your form, then start your animation block and put the view.frame to superview.bounds also giving it an animation time. I find that 2 seconds is ok. sometimes less.
at this point the presented view is inside the controller which is on the stack. Now while you cant directly modify the navigation controller within the presented view controller you could set a delegate that tells the original your intentions and therefore the presenting view controller (the one on the navigation stack) can push or pop the view controllers as requested. And the presented view controller will be pushed along with it.
Another positive point is that you can do much like other apps do, and present a semi modal view. With a partially transparent background. this way you can show things happening behind the view even tho they dont directly manipulate it.

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.

ios sdk only one view changes at a time bug

I'm pushing a number of views:
the top one is a UITabBarController
the second one is a UINavigationController with a pushed view
the third one is a modal box.
Once the close button in the modalbox is pressed I'm trying to revert everything to the default state and change the tabbar index.
[self dismissModalViewControllerAnimated:YES];
[self.navigationController popViewControllerAnimated:NO];
[self.tabBarController setSelectedIndex:3];
This dismisses the modal view but doesn't do anything else. Any ideas what could be wrong? I read something about a possible ios bug but I don't know how to work around it.
Neither UITabBarController nor UINavigationController is a view. Both are subclasses of UIViewController and have a property NSArray *viewControllers.
If you have an actualView controlled by an ActualViewController that is pushed on top of a rootView controlled by a RootViewController that is the rootViewController for the navigationController, and you also have a modalView controlled by a ModalViewController, then put
[self dismissModalViewControllerAnimated:YES];
in ModalViewController.m, and put
[self.navigationController popViewControllerAnimated:NO];
in ActualViewController.m (from whence modalView is pushed, presumably), and put
[self.tabBarController setSelectedIndex:3];
in RootViewController.m (from whence actualView is pushed, presumably).
If modalViewController was never added to the navigationController, then it doesn't know that the navigationController exists.
If actualViewController was never added to the tabBarController, then it doesn't know that the tabBarController exists.
The easy (and dirty) way:
Dismiss the modal view in the modal view. Make the navigation view controller the delegate of the modal view. Make the tabbar controller the delegate of the navigation controller. When the button is pressed call a method in the navigation controller that pops the view and calls a method of the tabbar controller which changes the selected tab.

Navigation controller problem

I have a normal view controller and I want to add a uinavigationcontroller to it so:
[self.view addSubview:aNavigationController.view];
everything works, fine, aNavigationController is an IBOutlet, in the XIB, it's view controller is loaded from another xib, then in the navigation controller's view controller's class I type this:
- (IBAction)anAction {
[self.navigationController pushViewController:aViewController animated:YES];
}
everything works fine, the view changes to the aViewController view and it's animated, but when I type in aViewController's class this:
- (IBAction)anotherAction {
[self.navigationController popViewControllerAnimated:YES];
}
it crashes, why?
Because there is no view to pop. When you're trying to pop view controller it is expected that there is some view in stack, i.e. the view from which you calling popViewControllerAnimated was already pushed earlier.
So popping is not just awesome animation but navigation through stack of views in navigation controller. In this particular situation you're trying to call -1st element of this stack, that is the reason of the crash.
Dig deeper here:
http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/NavigationControllers/NavigationControllers.html#//apple_ref/doc/uid/TP40007457-CH103-SW1