Best practice for switching between UINavigationController stacks in Storyboard - ios

In our storyboard we have multiple UINavigationController stacks. For example, the LoginViewController stack is completely separate from the SWRevealViewController stack.
What's the best practice for switching between them? When I press the logout button (the logout button is on the SWRevealController stack) and then try to present the LoginViewController stack, I get an error like this:
Warning: Attempt to present LoginViewController on SWRevealViewController whose view is not in the window hierarchy!
Even though I'm specifically setting self.window.rootViewController to the Login View Controller's UINavigationController in App Delegate like this:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Spitfire" bundle:nil];
UINavigationController *nav = [storyboard instantiateViewControllerWithIdentifier:#"LoginNavigationController"];
LoginViewController *loginVC = [storyboard instantiateViewControllerWithIdentifier:#"LoginViewController"];
self.window.rootViewController = nav;
[nav presentViewController:loginVC animated:NO completion:nil];
Is there a way I can "dismiss" the current UINavigationController stack and use a new one? Or maybe I shouldn't be calling this code in my app delegate?

I had not played with SWRevealViewController before your question, so my previous response was a little more generic. I've created a sample app here that does its best to throw around as many navigation stacks as possible; I'm sure I've covered your use case.
A few things I'll draw your attention to:
The "main flow" from my previous answer is right smack in the middle of the storyboard; that should help orient you.
SWRevealViewController.m can be used in conjunction with Storyboards and has a very clever hack to do so, but the documentation for it is buried in the implementation. It took me a while to figure out what the hell was happening, but my example app illustrates the usage (two segues, one for "front" and the other for "back", could have a third for "right").
The app's main flow gets completely swapped out for the alternate flow. This is by design of SWRevealViewController. You are essentially switching to an entirely new app.
All sub-flows that are presented modally disrupt the flow.
The author of SWRevealViewController has not provided UIStoryboardSegue subclasses for the replacement of front, rear, and right view controllers. This would be trivial to implement, but I've elected to do the swapping manually in FHKSettingsViewController's implementation.
I repeated code and made dinky classes to make sure what I was doing was obvious.

You should wait to call presentViewController until viewDidAppear on rootViewController.
See here for more discussion: whose view is not in the window hierarchy

Edit:
call presentViewController from your UINavigationcontroller's rootviewController in viewDidAppear: method
-(void)viewDidAppear:(BOOL)animated {
LoginViewController *loginVC = [storyboard instantiateViewControllerWithIdentifier:#"LoginViewController"];
[self presentViewController:loginVC animated:NO completion:nil];
[super viewDidAppear:YES];
}

Related

Which is the recommended way to segue from one view controller to another?

I like to segue from the current view controller to the "settings" view controller. Which method is more efficient to transition and why? Thanks! I have to segue in code because I have to observe a condition at run time.
Method 1:
UINavigationController *navigationController = (UINavigationController *)[[[UIApplication sharedApplication] delegate] window].rootViewController;
SettingsViewController *v = [self.storyboard instantiateViewControllerWithIdentifier:#"settings"];
[navigationController pushViewController:v animated:YES];
Method 2: In the storyboard, control-drag the current view controller icon (bottom left) to "settings" view controller and then name the segue identifier "gotoSettingsVC", set style to "push" and then use this code...
[self performSegueWithIdentifier:#"gotoSettingsVC" sender:nil];
Both work just fine.
If you're using storyboards, segues are probably the better way to go. Instantiate the initial view controller and let the segues be your guide.
Also, while Method 1 can be used with storyboards, unlike segues, it also can be used with regular xibs (or no interface builder at all) as well.
Other than that, it comes down to preference. Some people hate the interface builder and others swear by it. ;)

incorrect navigation controller sequence results in blank navigation bar

I am doing something wrong, but not sure what it is. Bellow is the diagram of my storyboard.
/----(push)----login \
Start nav ctrl mainview nav ctrl
\----(modal) register /
When the user goes through login an event is received by appdelegate logindone, in login done I do:
self.window.rootViewController = [self.window.rootViewController.storyboard instantiateViewControllerWithIdentifier:#"MainView"];
This works just fine.
However if the user goes through register and completes registration with login the same loginDone is triggered but the code above results in blank view.
If I replace above code with
UIStoryboard* storyBoard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
MainCtrl *mainViewController = (MainCtrl*)[storyBoard instantiateViewControllerWithIdentifier: #"MainView"];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:mainViewController];
self.window.rootViewController =navigationController;
This almost works, the view is displayed correctly, the only problem is that navigation bar is blank and it stays blank no matter where I go. Restarting app works of course because it bypasses register controller.
in register I do [self dismissViewControllerAnimated:NO completion:nil]; before calling loginDone, but this really makes no difference.
Any suggestions?
Edit:
self.window.rootViewController = [self.window.rootViewController.storyboard
instantiateViewControllerWithIdentifier:#"MainView"];
is the only thing that will work in normal flow i.e after login. Otherwise I get a crash "unavailable segue".
The way I fixed this is not exactly the way I wanted to fix it, but it works. I had to modify login done function that used to handle login finish work and transition to main view passing it parameter not to transition. On return I trigger a segue to main view from registration view. Still not sure why it was not working the way I had it done originally.

After using presentViewController existing segues can't be found

I am returning to my login view using the code below. The view loads correctly and everything looks fine. All buttons work etc.
JALoginViewController *loginVC = [[JALoginViewController alloc] init];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:loginVC];
navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
[self.navigationController presentModalViewController:navigationController animated:YES];
However, if a user tries to log in again, the segue that takes them to the next scene can't be found.
I'm using performSegueWithIdentifier if the users login credentials are correct, like this:
[self performSegueWithIdentifier:#"loginSegue" sender:self];
This is the error I receive:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Receiver (<JALoginViewController: 0x8d614b0>) has no segue with identifier 'loginSegue''
I've done lots of searching on Google and through the docs for the solution to this, the closet I've got (at least I think) is this question. The explain and solution sound like they could be correct and relevant, but I can't put them into practice.
Documents I've read and tried:
initWithRootViewController
popToRootViewController - The current root view controller is for a tab bar - not the login scene I need so as far as I'm aware I can't use this.
popViewControl
pushViewControl - This works to an effect, I don't think it is the correct way though. I don't want there to be navigation bar and I don't want my tab bar to appear when the user returns to the login scene.
I've tried various methods with limited / no effect. At this stage any help would be greatly appreciated.
Please let me know if I haven't provided enough information.
Thanks
JA
Edit - Zoomed out image of storyboard
![Zoomed out image of Storyboard][1]
On the basis of the screen snapshot of the revised question, from your rightmost red-highlighted scene, you should be able to:
[self.tabBarController dismissViewControllerAnimated:YES completion:nil];
and you'll be back at that initial screen (I'm assuming you did modal segue from initial screen to your tab bar controller).
Original answer:
If you want to manually push to a view controller, rather than creating it via alloc/init, you should use
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"loginsSceneStoryboardIdHere"];
or, if that view controller was the "initial" scene (the one with the simple arrow coming in from the left), you could use
UIViewController *controller = [self.storyboard instantiateInitialViewController];
And you shouldn't be manually creating the navigation controller, either. If the loginVC needs a navigation controller, you should embed that scene in a navigation controller right in the Interface Builder, then give that new navigation controller its own unique storyboard identifier, and then you can
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"navControllerSceneStoryboardIdHere"];
[self presentViewController:controller animated:YES completion:nil];
I must confess that I'm worried by this whole "return to login via pushViewController" construct. I assume you know that you're not "returning" to it, but creating a new copy of it. If you push/modal from A to B and then B to C and then C to A, you're holding 4 views and their controllers in memory, two copies of A and one of B and one of C (which is, obviously, not good). I just wanted to make sure you don't have a circular set of segues and/or push/presentViewController references.
If the login is the initial scene in your app and if you've been doing only push segues (no modal segues along the way), you can do a:
[self.navigationController popToRootViewControllerAnimated:YES];
That will take you to the top level view controller, and it will pop off and release all of the intervening scenes.
If you're using iOS 6, you can avail yourself of the unwind segue, which can achieve the same functionality, but it doesn't care whether the preceding segues were pushes or modals.
There are lots of ways of skinning the cat, but generally doing a new presentViewController to the first scene in your storyboard is a very bad idea.

Moving from one view to another iOS

I'm relatively new to iOS development. I am to move from one viewController to another I use a modal segue transition on button click. This is a game so i want to allow the user to click images to essential move the the app menus.
I have a main page that displays several images, on clicking one i want to be able to move to another view. Currently doing this with a modal segue is causing odd problems with my touchesEnded event where if, for example, i navigate to a page 3 times the touchesEnded event is fired 3 times.
Is there a better way for me to do this or am i just missing thing fundamental?
Thanks
Yes, I think you must make the the navigation controller your root view controller then push views accordingly
UINavigationController * nav = [[UINavigationController alloc] initWithRootViewController:YOUR_BASE_CONTROLLER]
self.rootViewController = nav;
this is in your app delegate.
Then in your action method
[self.navigationController pushViewController:secondViewController animated:YES]
Im assuming you are using the Storyboard to link VCs using segues.
Modal segues are great for simple transitions but really seem to limit what you can accomplish when they are just linked through SB. Ive found that creating an IBAction that includes the following for a VC segue will allow you to not only control your segues more efficiently but also allow you to have a clearer view of what is actually occurring during the transition.
-(IBAction)goToVc:(id)sender{
//Other code to take place during the segue here
//This will identify the Storyboard in use
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
//This will identify the View Controller to switch to
SecondViewController *vc2 = [storyboard instantiateViewControllerWithIdentifier:#"SecondViewControllerID" ];
[self presentViewController:vc2 animated:YES completion:NULL];
}
Every time you perform a modal segue, you lose the UINavigationController that you were previously using. What you need to do is embed a UINavigationController in the view that you are performing a modal segue to.
Check out a question I answered the other day to help you visualize more clearly what I'm talking about.

Suggestions for popping complex navigation path to root (path includes multiple nested UINavigationControllers and modal overlays)

I'm working on an application that includes relatively complex navigation paths through a storyboard of views. The scenario is, I have a UINavigationController with a number of controllers, each of which can potentially present modals that themselves have UINavigationCollers which have their own sets of controllers that can present modals, etc. etc. etc. My problem is that I need a general way to essentially cancel out and pop all the way back up to the root view controller.
Would you suggest a mechanical approach that backtracks view controllers via a mix of popToRootViewControllerAnimated and dismissViewControllerAnimated calls? Or is there an easier way to jump back to my root given this complex path?
This feels like it would be a common problem. Any suggestions would be much appreciated!
Note, I've seen related questions - I already know how to perform the necessary steps to pop the controllers when the path is known. What I'm looking for is a way to do this in a general way.
Thanks!
If you just call popToRootViewacontroller on your your main navigation controller child that should be all you need.
You could create a class which implements UINavigationControllerDelegate and have all the UINavigationControllers set a single instance of that class as a delegate. Then, if you know the view controller you want to pop to, you can use
– (void)navigationController:(UINavigationController *)nc
willShowViewController:(UIViewController *)vc
animated:(BOOL)animated;
If you're not at the desired view controller yet, keep popping.
I ended up solving this problem by manipulating the UIWindow.rootViewController property. This allows me to quickly pop back to the root from an arbitrary view controller. From the docs: "If the window has an existing view hierarchy, the old views are removed before the new ones are installed." Thanks to the folks who responded.
- (void)popToRootViewControllerFromViewController:(UIViewController *)fromViewController
{
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
UIViewController *rootView = [mainStoryboard instantiateViewControllerWithIdentifier:#"RootViewController"];
[fromViewController presentViewController:rootView animated:NO completion:^{
self.window.rootViewController = rootView;
}];
}

Resources