Storyboard - Pop to Root UIViewController Without UINavigationController? - ios

I have a UIViewController hierarchy that consists of a Root View Controller, which segues to a UITabBarController which has several UIViewControllers under it. Each of those tab UIViewControllers has its own UINavigationController. There is no shared UINavigationController, and none applying to the Root View Controller. Now I have a situation in which I need to pop all the way back to the Root View Controller from one of the tab UIViewControllers. However, since the Root View Controller and the tab UIViewControllers do not share a common UINavigationController, I am unable to simply call [self.navigationController popToRootViewControllerAnimated:YES]. Is it possible to pop to the Root View Controller (or unwind my Segues back to the root programmatically) without a shared, common UINavigationController?

I had a similar problem once, try this
UIStoryboard *storyBrd = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *controller = nil;
controller = [storyBrd instantiateInitialViewController];
self.view.window.rootViewController = controller;
This initial view controller is your RootViewController. Else you can also use the method
[storyBrd instantiateViewControllerWithIdentifier:<View Controller's Restoration ID>]

Here's how I understand the vc arrangement:
|-navvc
| |--rootvc-(pushes)-stack...
"root" vc-(presents)->tabvc--|
|-navvc
|--rootvc-(pushes)-stack...
Its easy to unwind all of this so long as you're holding the handles to the right stuff. The navigation vcs within the tabs can be accessed by any view controller on their stacks with self.navigationController.
You'll need to have ahold on either the tab bar vc or the one you call "root" vc, also. You can arrange that with a property on your app delegate.
The only other tricky thing to remember is that the transition from the "root" vc to the tab bar is a present, not a push, so it must be undone with a dismiss, not a pop. To express this in code, lets say you've got a handle to the tab bar vc on your app delegate....
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (strong, nonatomic) UITabBarController *tabBarVC;
- (void)popEverything:(BOOL)animated;
#end
// AppDelegate.m
// get the tab bars tabs (which are presumed to be navigation vcs)
// dismiss the tab bar vc (which was presumed to be presented)
// iterate the navigation vcs, popping all of them to root
- (void)popEverything:(BOOL)animated {
NSArray *viewControllers = self.tabBarVC.viewControllers;
[self.tabBarVC dismissViewControllerAnimated:animated completion:^{
for (UINavigationController *navVC in viewControllers) {
[navVC popToRootViewControllerAnimated:animated];
}
}];
}
Caveats apply: still guessing a little about your vc arrangement, and haven't tested the foregoing.

Related

Objective C: How to get navigation controller from ViewController behind my current ViewController

I have ViewController 1 with navigation controller where I'd like to push some other VC. But I have ViewController 2 presented over it not in navigation controller hierarchy.
In ViewController 2 I have some methods to minimise it, but I can't reach navigation controller in ViewController 1 to push other VC there. I tried to do it from window with code:
UITabBarController *mTabBarVC = (UITabBarController *) [UIApplication sharedApplication].keyWindow.rootViewController;
UINavigationController *prevNavigationController = myTabBarVC.selectedViewController.navigationController;
But I had no success I get nil in prevNavigationController.
Create in ViewController 2.h:
#property UIViewController *parent;
In ViewController 1 pass self:
viewController2.parent = self;
In ViewController 2 you can now get UINavigationController:
UINavigationController *navVC = self.parent.navigationController;
It's the best way, because you can change structure of your view controllers in future and will get a bug.
If ViewController 2 present modally on ViewController 1, you can get VC1 from VC2 in self.presentingViewController property.

Can't push to View Controller from AppDelegate without losing Navigation Bar and Tab Bar setup iOS

I try to push to specific view controller from app delegate without losing previously set navigation bar and tab bar items setup.
As presented in my Main storyboard:
After 1 action performed in TypeNameVC, on app relaunch I want to skip LogInVC and TypeNameVC in didFinishLaunchingWithOptions in AppDelegate forward to MapViewController or any other VC of Tab Bar Controller.
This code initializes new Navigation Controller with new Navigation Bar and Tab Bar Controller is missing.
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
//To instantiate Main.storyboard.
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
//To instantiate VC to go to.
MapViewController *mapVC = (MapViewController *)[storyboard instantiateViewControllerWithIdentifier:#"MapViewControllerID"];
//To push to instantiated VC.
[navigationController pushViewController:mapVC animated:YES];
I tried to performSegue using segue to Tab Bar Controller but on Navigation Controller stack there is only LoginViewController (I checked it with [navigationController viewControllers])
I read through entire Stack Overflow, I made dozens of combinations but I can't figure out the solution. I would appreciate any help/advice.
UPDATED QUESTION
Screen: screen
Have a look to the screen image. First row of screen shows how it looks like if I navigate "normally". Second row shows what happens if I push to MapVC from AppDelegate. What I need is to skip first two VC's with maintaining Navigation and Tab Bar bars setup.
When I navigate "normally" Tab Bar Controller and it's VC's get correctly allocated.
2016-06-08 12:37:44.394 Checkpoint[4196:1034786] Navigation Controller VC's: (
LogInViewController: 0x135d50b00,
TypeNameViewController: 0x135f455b0,
UITabBarController: 0x135f1d180
)
2016-06-08 12:37:44.394 Checkpoint[4196:1034786] Tab Bar Controller VC's: (
MapViewController: 0x137078cf0,
PlacesTableViewController: 0x135f1d7d0,
FriendsTableViewController: 0x137083350
)
When I push directly to MapVC they don't. I do get the point what's going on but I know how to code that solution.
Navigation Controller VC's: (
LogInViewController: 0x15fd5ccf0,
MapViewController: 0x15fd6ffa0
)
2016-06-08 12:31:53.813 Checkpoint[4187:1033755] Tab Bar Controller VC's: (null)
Customize your class MapViewController for visible Navigation Bar and Tab Bar:
- (instancetype)init
{
// init code
self.hidesBottomBarWhenPushed = NO;
retur self;
}
-(void)viewWillAppear:(BOOL)animated
{
// some code
[self.navigationController setNavigationBarHidden:NO];
}

UINavigationController of UIViewController is nil, after pushViewController

I'm using the ECSlidingViewController for my navigation menu, whenever I want to push a UIViewController from this menu, the UINavigationController of the pushed UIViewController is always nil.
The UINavigationController is initialized, the NSLog output shows the following <UINavigationController: 0x8a80770> address. When I call the method pushViewController:animated the UIViewController gets pushed but the UINavigationController is nil, therefore I can't see the UINavigationBar in this controller.
Here is the code snippet I'm using for this:
RecommendationsViewController *rvc = [self.storyboard instantiateViewControllerWithIdentifier:#"RecommendationsViewController"];
[self.transitionsNavigationController pushViewController:rvc animated:NO];
self.slidingViewController.topViewController = rvc;
In viewDidLoad the transitionNavigationController get's initialized with (please note the slidingViewController is from the ECSlidingViewController project on github https://github.com/ECSlidingViewController/ECSlidingViewController and is of type ECSlidingViewController):
self.transitionsNavigationController = (UINavigationController *)self.slidingViewController.topViewController;
Thanks for any help!
I think you have misunderstood how this is suppose to work.
The UINavigationController has to be the topViewController.
Don't reassign the topViewController after you do a push. By doing this:
self.slidingViewController.topViewController = rvc;
All that is going to do is set the current window to display that UIViewController, thats why you didn't see the nav bar, the app needs to display the UINavigationController which in turn will manage a list of UIViewController's
The navigation controller handles a stack of viewControllers, just push the new UIViewController and nothing else
There is a related issue where a Navigation controller's topViewController will forget that it is attached to a navigationController.
My Storyboard setup is: ->NavigationController->ViewController
The connection between NavController and ViewController is "root view controller".
I have set a storyboardID for each of these view controllers.
I have a view management class "ViewManager" that contains weak references to all storyboard views, which I obtain using:
_rootNC = [self.mainStoryboard instantiateViewControllerWithIdentifier:#"NavController"];
//ViewController gets auto-attached to the NavController, and so viewController.navigationController == NavController
_firstVC = [self.mainStoryboard instantiateViewControllerWithIdentifier:#"ViewController"];
//Instantiating the ViewController again clears its navigationController property, and so viewController.navigationController == nil
I suppose one shouldn't gain a hook into Storyboard Instances by reinstantiating the views. I'd appreciate if others would share their best-practices for obtaining weak references to storyboard viewControllers in such a way that I could control them in a single viewManager class. (I'm leaning toward setting viewManager.rootNC from within NavigationController's viewDidLoad).

Back to RootViewController from Modal View Controller

From Home view - my RootViewController - I open up 2 ViewControllers one after another as user progresses in navigation hierarchy like so:
1) SecondViewController is pushed by button connected in my Storyboard
2) ThirdViewController is presented modally
[self performSegueWithIdentifier:#"NextViewController" sender:nil];
So, the picture is: RootViewController -> SecondViewController -> ThirdViewController
Now in my ThirdViewController I want to have a button to go back 2 times to my RootViewController, i.e. go home. But this does not work:
[self.navigationController popToRootViewControllerAnimated:YES];
Only this guy goes back once to SecondViewController
[self.navigationController popViewControllerAnimated:YES];
How can I remove both modal and pushed view controllers at the same time?
I had a similar situation, where I had a number of view controllers pushed onto the navigation controller stack, and then the last view was presented modally. On the modal screen, I have a Cancel button that goes back to the root view controller.
In the modal view controller, I have an action that is triggered when the Cancel button is tapped:
- (IBAction)cancel:(id)sender
{
[self.delegate modalViewControllerDidCancel];
}
In the header of this modal view controller, I declare a protocol:
#protocol ModalViewControllerDelegate
- (void)modalViewControllerDidCancel;
#end
And then the last view controller in the navigation stack (the one that presented the modal view) should implement the ModalViewControllerDelegate protocol:
- (void)modalViewControllerDidCancel
{
[self dismissViewControllerAnimated:NO completion:nil];
[self.navigationController popToRootViewControllerAnimated:YES];
}
This method above is the important part. It gets the presenting view controller to dismiss the modal view, and then it pops back to the root view controller. Note that I pass NO to dismissViewControllerAnimated: and YES to popToRootViewControllerAnimated: to get a smoother animation from modal view to root view.
I had the same requirement but was using custom segues between the view controllers. I came across with the concept of "Unwind Segue" which I think came with iOS6. If you are targeting iOS6 and above these links might help:
What are Unwind segues for and how do you use them?
http://chrisrisner.com/Unwinding-with-iOS-and-Storyboards
Thanks.
Assuming your AppDelegate is called AppDelegate, then you can do the following which will reset the rootviewcontroller for the app window as the view RootViewController
AppDelegate *appDel = (AppDelegate*)[[UIApplication sharedApplication] delegate];
RootViewController *rootView = [[RootViewController alloc] init];
[appDel.window setRootViewController:rootView];

Setting up a UINavigation controller and various view loads

I have setup a UINavigation controller that uses the AppDelegate as the main point of contact.
I have different methods which run such as presentHomeViewController, presentLoginViewController, which push the different view controllers to the Navigation Controller.
App Delegate - didFinishLaunching
welcomeViewController = [[MyWelcomeViewController alloc] initWithNibName:#"MyWelcomeViewController" bundle:nil];
navController = [[UINavigationController alloc] initWithRootViewController:welcomeViewController];
navController.navigationBarHidden = YES;
self.revealSideViewController = [[PPRevealSideViewController alloc] initWithRootViewController:navController];
[self.revealSideViewController setDirectionsToShowBounce:PPRevealSideDirectionNone];
[self.revealSideViewController setPanInteractionsWhenClosed:PPRevealSideInteractionContentView | PPRevealSideInteractionNavigationBar];
self.window.rootViewController = self.revealSideViewController;
Is this the correct process for this?
- (void)presentHomeViewController {
// We start by dismissing the ModalViewConrtoller which is LoginViewController from the welcomeview
[self.welcomeViewController dismissModalViewControllerAnimated:YES];
// Check if the home view controller already exists if not create one
if (!self.homeViewController) {
NSLog(#"presentHomeViewController- Creating the Home View controller");
homeViewController = [[MyHomeViewController alloc] initWithNibName:#"MyHomeViewController" bundle:nil];
}
// Push the homeViewController onto the navController
NSLog(#"presentHomeViewController");
self.navController.navigationBarHidden = NO;
[self.navController setTitle:#"Home"];
[self.navController pushViewController:homeViewController animated:NO];
If I then add the following to a different class :
[self.navigationController pushViewController:accountViewController animated:NO];
No view is pushed to the stack, should I control all the movement within the AppDelegate as I have been doing, or is there betters way to approach this?
EDIT
Thanks for posting your code. So, to address your final question first, I don't recommend controlling your navigation stack from the app delegate. You should be controlling the stack from the view controllers that are the children of the navigation controller.
To that point, remember the hierarchy of view controllers: UINavigationController inherits from UIViewController, and UIViewController has properties defined for all the things you'd see in a navigation layout such navigation items and title. More importantly, it also has properties for its parent view controllers, the view controller that presented it, and its navigation controller. So, considering the hierarchy, your app delegate should only instantiate the navigation controller's root VC and the nav controller itself, and then subsequently set the nav controller's root VC.
From there, you should be pushing and popping other VCs from the VCs themselves. Remember, every VC has a property that's automatically set to point at the navigation controller it's a part of. That's why [self.navigationController pushViewController:] works. For instance, if I have a nav controller whose root VC is a UITableViewController, and tapping on one of the items in the table view pushed a new VC onto the stack, I would push that VC from the table VC and not from the nav controller class or the app delegate.
Sorry if that's confusing. Please let me know if that needs clarification and I'll give it my best. Otherwise, hopefully that gets you on the right track.

Resources