Navigation controller problem - objective-c

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

Related

Trying to understand dismissing ViewControllers

I was trying to dismiss a view controller and there are several methods to call that all seem to similar. #1 worked but the others didn't. When should I use one of these but not the other?
1. [self dismissViewControllerAnimated:YES completion:nil]; (this one worked).
2. [self.parentViewController.navigationController popViewControllerAnimated:YES];
3. [self.navigationController popViewControllerAnimated:YES];
4. [self performSegueWithIdentifier:#"showPreviousController" sender:self];
5. [self.navigationController popToRootViewControllerAnimated:YES];
[self dismissViewControllerAnimated:YES completion:nil];
This works fine if you don't have navigation controller and you want to jump back to previous view controller.
Next button jumps to view controller B and when dismiss button is pressed it call's dismissViewControllerAnimated: method and jumps back to first view controller.
[self.parentViewController.navigationController popViewControllerAnimated:YES];
Quote from Apple's documentation about parentViewController:
If the recipient is a child of a container view controller, this property holds the view controller it is contained in. If the recipient has no parent, the value in this property is nil.
[self.navigationController popViewControllerAnimated:YES];
This jumps back to previous view controller. You need navigation controller set up to use this method. Otherwise nothing happens.
If you press Dismiss button in view controller B and that button calls popViewControllerAnimated: method, it will jump back to view controller A.
[self performSegueWithIdentifier:#"showPreviousController" sender:self];
This performs seque with identifier as it says. In storyboard you can set identifier for seques.
When you have seque selected, you can set identifier in Attributes inspector:
[self.navigationController popToRootViewControllerAnimated:YES];
This jumps back to first view controller. Called root controller. So if you have 5 view controllers (A -> B -> C -> D and E) and you call popToRootViewControllerAnimated: at E, it will jump back to A-controller.
Those methods all do different things, I suggest you read the View Controller Programming Guide, since you are mixing up several concepts.
However here's a summary of what they do:
[self dismissViewControllerAnimated:YES completion:nil]; (this one worked).
Dismiss the view controller presented by this controller (self).
[self.parentViewController.navigationController popViewControllerAnimated:YES];
Pop the top view controller in the parent's navigation controller. This is weird and probably wrong. parentViewController is the view controller the current one is embedded in. You are accessing its navigation controller and making it pop the view controller on top of the stack. It will silently fail in case:
parentViewController is nil
navigationController is nil
[self.navigationController popViewControllerAnimated:YES];
Pop che view controller on top of the current navigation stack. Makes sense in case the current controller has been pushed by the navigation controller (and not presented modally).
[self performSegueWithIdentifier:#"showPreviousController" sender:self];
Well, this is performing the showPrviousController segue, whatever it does...
[self.navigationController popToRootViewControllerAnimated:YES];
This make the current navigation controller to pop all the view controllers, except the root one, which will remain on top.

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];

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.

UISplitViewController not calling delegate methods while pushing new detailView

I setup a storyboard based on the Master-Detail Application, embed the detail view in a navigation controller, and add a new table view controller object which I will use as a second detail view controller.
I then push the new detail view controller with the following code (instead of a segue because I am pushing both a root view and a detail view controller at the same time. Only the detail view code is shown).
// Push the detailView view controller:
NewClass *newViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"test"];
newViewController.navigationItem.hidesBackButton = YES;
self.splitViewController.delegate = newViewController;
[self.detailViewController pushViewController:newViewController animated:YES];
This works perfectly, EXCEPT that the splitView delegate methods are never called before or after the push. If I do this while in portrait mode, after it pushes the detailViewController, the button to drop down the masterView popover does not show up UNTIL I rotate to landscape mode and then back to portrait mode.
How can I cause the willHideViewController/willShowViewController split view controller delegate methods to be called or manually cause them to be called?
So from what I found, it doesn't call the method because the orientation hasn't changed.
What you have to do is to pass the button from the presenting view controller since it's already tied to the popover like this:
if(self.navigationItem.leftBarButtonItem != nil) {
newViewController.navigationItem.leftBarButtonItem = self.navigationItem.leftBarButtonItem;
}
// Push the newViewController

NSNotificationCenter for presenting multiple modals?

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.