Uncertainty with instances in navigation: UITabBarController - ios

In my app, I have a UITabBarController that is set as the initial view controller and has the storyboard ID 'TabBarControl'. TabBarControl has 4 tabs, and off of the second tab(index 1) there is a sequence of UIViewController navigations that goes as such, where VC is ViewController..
UITabBarController > UINavigationController('branchesControl') > VC1('branchesView') > UINavigationController > VC2 > UINavigationController > VC3 > UINavigationController > VC4
I have a UIBarButtonItem named 'Confirm' on the navigation bar in VC4. The Confirm button triggers the following IBAction method:
- (IBAction)confirmClicked:(UIBarButtonItem *)sender
{
//EXECUTE NAVIGATION
UITabBarController * tabControl = [self.storyboard instantiateViewControllerWithIdentifier:#"TabBarControl"];
tabControl.selectedIndex = 1;
[self presentViewController:tabControl animated:YES completion:nil];
}
The goal is to navigate from VC4 back to the original 'branchesView'
The problem is, I've noticed that a manual set of a color of a UIBarButtonItem on VC2's navbar resets to a default white after hitting Confirm and then revisiting the view stack. I believe that I am creating multiple instances.
How can I simply navigate back to branchesView, and then VC2 without creating new instances?
PS I'm not sure if the new instance is TabBarController or or branchesView, I just know that it would seem that I am creating multiple instances somehow with the confirmClicked: method.

Instead of going back to the existing branchesView, you are presenting a new UITabBarController with a whole new set of child view controllers which is almost certainly not what you want to be doing.
Instead, you need to pop back to the already existing view controller that you want to get back to. You can do this either by reference or index, and in your case, by simply popping to the root (provided the view controller you want to get to is indeed at the root).
You can simply do:
[self.navigationController popToRootViewControllerAnimated:YES];
Your question also makes it seem like each view controller in the stack is wrapped in a new navigation controller. if that is the case, you will need to fix that as well. You should not nest UINavigationControllers. Instead, have a single one as the root for each tab and each time you want to push a new UIViewController onto the stack, you call:
[self.navigationController pushViewController:viewControllerToPush animated:YES];

Related

Start second view controller of navigation stack first

There is a default calendar app.
It starts with the next view controller and back button is already there like there some other view controller was started before this one:
When you press back button you get the next view controller:
How did they do it?
In my app I need the same logic (to start a view controller with the latest or default category but users can press back button to select a different category)
If I were to do this, I would start by simply using pushViewController(animated:) to push the month view onto the navigation stack, with animated: false in the root view controller's viewWillAppear(animated:) method. The calendar would appear to the user already one level deep in the navigation stack.
So, the first controller is the year view, and then the month view is the second one pushed onto the stack, but it all happens before the user has seen any of the views. Simple, right?
Here are the docs for UINavigationController in case that helps.
I think what you want is to push the view controllers once at start. An easy way to do it is to sub-class UINavigationController and assign it to the root navigation controller in your storyboard. Then simply do the work in your sub-class' viewWillAppear method, as this will be called exactly once at startup.
Of course, you can also accomplish the same result by using a flag to only load the next view controller once if you put the push code in the first view controller's viewWillAppear.
#interface MyNavigationController : UINavigationController
#end
#implementation MyNavigationController
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
UIViewController *secondVC = [self.storyboard instantiateViewControllerWithIdentifier:#"secondVC"];
[self pushViewController:secondVC animated:NO];
}
#end

ECSlidingViewController with Push and Unwind Segue

I am using ECSlidingViewController with storyboards. ECSlidingVC is my root (starting) controller. My left menu is a TableView with static cells, and my TopViewController is a navigation controller. I want to have a single NavigationController for all my app.
From my left menu i cant use push or unwind segues, i understand that part though. i can only use ECSlidingSegue which changes topviewController of ECSlidingVC and which destroys my navigation controller and it's stack.
i want to be able to go back from a menu item VC to previous VC in my main nav controller. lets say basically i want ECSlidingVC to not change topViewController but push destination viewController to my source.topViewController.navigationController.
Also i need to use unwind segues with my menu items. i need to go back to a VC in my main nav controller.
i inspected ECSlidingSegue source code and all it does is to replace topViewController.
is there a built in method (or segue) in ECSlidingViewController for pushing (or unwinding) VC into source.topViewController.navController or do i need to implement a custom segue myself?
I think the best way to go is for you to implement a custom segue yourself. Something like ECSlidingNavigationSegue, which would look for your topViewController, then check whether it's a UINavigationController and then push the destinationController to it.
It's basically the same perform method as the ECSlidingSegue, but with this feature of pushing a controller to the topViewController instead of replacing it.
Good luck!
In case someone haven't found answer, I did it in this way.
1- #import "UIViewController+ECSlidingViewController.h" to your menuViewController
2- Set stroboardID of your destinationViewController to "someID"
3- When triggering some action, in backend, use this code:
if(self.slidingViewController.currentTopViewPosition == ECSlidingViewControllerTopViewPositionCentered){
[self.slidingViewController anchorTopViewToRightAnimated:YES];
}
else{
self.slidingViewController.topViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"someID"];
[self.slidingViewController resetTopViewAnimated:YES];
}

Complications that may arise from setting UITabBarController's selectedIndex property

Sometimes in my app that I'm developing, I find myself in a position where I just need to navigate back to a specific tab view of my UITabBarController. When I do this I usually just change the tabBarController's selectedIndex and then use presentViewController: on the tabBarController, like so:
UITabBarController * tabControl = [self.storyboard instantiateViewControllerWithIdentifier:#"TabBarControl"];
tabControl.selectedIndex = 1;
[self presentViewController:tabControl animated:YES completion:nil];
Will this cause instance problems? As in.. will doing this simply create another instance of the tab bar tab that I have navigated to, seemingly "reloading" that tab bar view?
As the method name in your code suggests, you will be creating a new view controller object every time you are navigating to a specific tab view. You should only keep a single UITabBarController object in your app, probably in AppDelegate or a singleton object, and use that object for navigation.

Cant make navigation back to original tab without creating a new instance

Here is my set up in storyboard.
I'm trying to use this code: (In an IBAction connected to the UIBarButtonItem in the last VC):
- (IBAction)confirmClicked:(UIBarButtonItem *)sender
{
//EXECUTE NAVIGATION
UITabBarController * tabControl = [self.storyboard instantiateViewControllerWithIdentifier:#"TabBarControl"];
tabControl.selectedIndex = 1;
[self presentViewController:tabControl animated:YES completion:nil];
}
to navigate from the last ViewController in this picture to that very first UINavigationController in the stack. I was informed that UINavigationControllers are not meant to be nested like this, and that only one UINavigationController should be necessary, but when I successfully remove(which I have done at least 5 times) the other three UINavigationControllers from the rest of the stack, I completely lose my UINavigationBars from the regular ViewControllers.
The problem with navigating the way that I currently am with my 'confirmClicked:' method, is that I create another instance of that first UIViewController in the hierarchy.
Should I:
A) Remove all the unnecessary UINavigationControllera in the view hierarchy, then use 'popToRootViewController:' to correctly navigate from the last VC to the first?
B) Try to navigate another way from the last VC to the first VC, keeping the extra UINavigationControllers in the view hierarchy?
Also, if I remove the Navigation Controllers, how will I keep my navigation bars in the UIViewControllers, since they have been disappearing in the past when removing the Navigation Controllers?
Sorry in advance for the many questions but I have been stuck for a while.
OK
First thing
Remove all those navigation controllers. You only need one.
Second
The code in that IBAction is creating a brand new instance of the TabBarController and placing it over the current stack (this is how to get memory problems).
Third
What you probably want is either an unwind segue (possibly) or code something like this...
- (IBAction)confirmClicked:(UIBarButtonItem *)sender
{
//EXECUTE NAVIGATION
UITabBarController * tabControl = [self.tabBarController setSelectedIndex:1];
}

How do I emulate 'Notes' from Apple and start the app with a navigation controller with a 'back' view?

I have an application that has a login page. It then moves to a navigation controller that has a collection view as its root view controller. When the app starts and there is only one item in the collection, I want to have the navigation controller automatically push to that item and allow the user to use 'back' to view the collection. This is the same behavior that is in 'Notes' from Apple.
The idea is to allow the user to immediately start to use the app and only 'discover' the need for the collection view after using the app for some time.
I am using IB, storyboards, and segues for my view transitions.
If I programmatically have the root view controller do a performSegue in its viewWillLoad: I get an error about causing a transition while a transition is still in process.
If I move the code calling performSegue into the didLoad, then the user sees a double transition.
A navigation controller usually manages the underlying stack itself. That is, you pass it an initial view controller, and then through segues or pushViewController:animated: the stack is altered. However, there is nothing stopping you from manually altering the stack. In prepareForSegue:sender:, you can check the number of items, and if it's 1, do something like this:
UIViewController *singleItemViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"SingleItem"];
NSMutableArray *viewControllers = [sender.destinationViewController.viewControllers mutableCopy];
[viewControllers addObject:singleItemViewController];
sender.destinationViewController.viewControllers = viewControllers;
Now instead of starting at the first view controller of the stack, you start at the second one, with a back button to navigate back.
Actually, I found that the following worked the best for me.
if (/* reason to change back list*/) {
UIViewController * rootViewController = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"backViewController"];
NSArray * viewControllers = #[rootViewController, self];
[self.navigationController setViewControllers:viewControllers animated:NO];
}
setViewControllers seems to be the best way to manipulate the stack.

Resources