I've got a tabbar controller as a main screen in my app. Some of the tabs got navigation controller embedded.
Here is my problem:
First tab is the initial one when app starts. Under certain conditions user should see second screen B (on navigation stack) immediately after app starts (there is performSegue which triggers in viewWillAppear of first screen). This works as it should. User starts the app and immediately sees the second screen. This also works when user switches to different tab and back. The problem is when user already is on the first tab and taps on it again. Then the stack gets destroyed users sees first screen A which will animate into second screen B in short order. This transition is clearly visible by user.
First tab --> screen A --> screen B --> ...
|
Second tab --> screen T --> screen U --> ...
|
...
So the question is how to prevent this behaviour? User shouldn't see the transition between A and B in this case.
Thanks
Why don't you change your code from Screen A to perfrom segue to next next Screen B unser CERTAIN condition.,
Just use that CERTAINcondition in TabbarController Class ( Subclass of Tabbar) in method like,
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
if (tabBarController.selectedIndex == 0)
{
if (CERTAIN condition True) {
UITabBar tabbar = self.tabBarController.tabBar;
NSArray *controlrs = self.viewControllers;
NSMutableArray *controllerCopy = [controlrs mutablecopy];
SCREEN_B_class *bClassObj = . .. //Tab 1 class Just Screen B, not Screen A -> ScreenB
[controllerCopy replaceObjectsAtIndex:[NSIndexSet indexSetWithIndex:0] withObjects:[NSArray arrayWithObjects:bClassObj]]
tabBarController.viewControllers = controllerCopy;
}
}
}
Hope this helps you,
HTH, Enjoy Coding!!
That's the expected behavior of UITabBarController, it will pop the navigation controller to the first view controller in its stack.
If you don't want the screen A to appear under certain conditions when user taps the tab bar item, just remove the first view controller from the navigation stack, doing something like this after pushing from A to B (form instance in the viewDidLoad: of B view controller):
NSMutableArray *allViewControllers = [NSMutableArray arrayWithArray: navigationController.viewControllers];
[allViewControllers removeObjectAtIndex: 0];
navigationController.viewControllers = allViewControllers;
This way screen B will be the root view controller of your navigation stack, which is what you want.
Related
I have view A, view B and view C. I have pushed view B from view A and push view C from view B.
When user tap on back button in view C, I call popToRootViewControllerAnimated so that user won't see view B at all.
Problem is that if user swipe back in view C, they will still see view B. I don't want user to see view B at all and jump directly to view A. How shall I do?
I use xib currently.
You can change stack of NavigationController when ViewController C appears. Use below code:-
NSMutableArray *aMutArr = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
[aMutArr removeObjectAtIndex:aMutArr.count-2];
self.navigationController.viewControllers = aMutArr;
I have removed ViewController B from the stack. So if your user swipes back he will be able to see ViewController A.
you can turn off the swipe back feature
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
in that way the user can only go back by the button that you have provided
Question
In a UISplitViewController collapsed display, how can I programmatically get back to master view controller?
Detail
I googled it but found no solution. Not sure if I was using the right keyword. This is how I show the detail view controller:
[self showDetailViewController:[[UINavigationController alloc] initWithRootViewController:detail] sender:self];
I also tried these 3 methods respectively, but none of them worked:
if (self.splitViewController.collapsed) {
UIBarButtonItem *backButtonItem = self.navigationItem.leftBarButtonItem;
(1):[backButtonItem.target performSelector:backButtonItem.action];
(2):[[UIApplication sharedApplication] sendAction:backButtonItem.action to:backButtonItem.target from:nil forEvent:nil];
(3):objc_msgSend(backButtonItem.target, backButtonItem.action);
}
navigation items set like thie in detail VC viewDidLoad:
self.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
self.navigationItem.leftItemsSupplementBackButton = YES;
Alright, I have found a solution that seems to work. I have tested it on iPhone 6 and iPhone 6 Plus, but I only just discovered it thirty minutes ago, so It might have some unfortunate side effect which I have not run into yet.
It's in swift. I hope it's clear though. Let me know if you need me to provide it in Objective-C instead.
if let splitViewController = splitViewController {
if splitViewController.collapsed {
let viewControllers = splitViewController.viewControllers
for controller in viewControllers {
// PrimaryNavigationController is the navigation controller I use
// as the split views master view, which is also set as its delegate
// but it could be any UINavigationController that is the
// primary controller of the split view
if controller.isKindOfClass(PrimaryNavigationController) {
controller.popViewControllerAnimated(true)
}
}
}
}
I call this from my detail view when I want to dismiss it.
The code works by checking if the split view controller is collapsed, which is the only state where popping the detail view makes sense (to me anyways). Then it simply looks for the navigation controller currently in play in the split view controller and asks it to pop it's top view controller. This works because when in collapsed mode, the split views master view is the only view controller in the stack. The detail view is collapsed "into" it, and therefore becomes the current top view controller of it, thus is the one that gets popped.
Seems to work. Let me know if it do for you too.
I was looking to do exactly the same, and this code worked for me. I put it in the detail view, hooked up to a button in the navigation bar.
In my application the detail view can segue to itself a number of times and this code gets one back to the master view no matter how deep down the line it gets.
#IBAction func unwindSegueId(sender: AnyObject) {
if (self.splitViewController!.collapsed) {
self.splitViewController!.viewControllers[0].popToRootViewControllerAnimated(true)
}
}
This seems to work (provided you have a navigation controller in your master pane)
if (self.splitViewController.collapsed) {
[(UINavigationController *)self.splitViewController.viewControllers[0]
popToRootViewControllerAnimated:YES];
}
I have three View Controllers in a Navigation Controller. One is the root, and the others should appear if conditionA or conditionB is met, respectively.
In case both conditions are true, I want the conditionA View Controller to be shown. If things then change, and conditionA is no longer met, but conditionB still is, I want to show the conditionB View Controller. This means that I want to be able to go from the conditionA View Controller to the conditionB View Controller, and vice versa, until neither condition is met. However, I always want the back button to send users to the root.
To implement this while avoiding errors like ‘Finishing up a navigation transition in an unexpected state’, I’ve taken code from Dave DeLong’s answer https://stackoverflow.com/a/11821263/2524427.
(void)somethingChanged:(NSNotification*)notification
{
if(!conditionA && !conditionB){return;}
NSArray *viewControllers = self.navigationController.viewControllers;
NSMutableArray *newViewControllers = [NSMutableArray array];
// preserve the root view controller
[newViewControllers addObject:[viewControllers objectAtIndex:0]];
if(conditionA)
{
// add the new view controller
[newViewControllers addObject:[self.storyboard instantiateViewControllerWithIdentifier:#"conditionAViewController"]];
// animatedly change the navigation stack
[self.navigationController setViewControllers:newViewControllers animated:YES];
return;
}
if(conditionB)
{
// add the new view controller
[newViewControllers addObject:[self.storyboard instantiateViewControllerWithIdentifier:#"conditionBViewController"]];
// animatedly change the navigation stack
[self.navigationController setViewControllers:newViewControllers animated:YES];
}
}
This works almost perfectly. However, if I have the following sequence
somethingChanged triggered while only conditionB is met -> conditionB View Controller appears - as desired
somethingChanged triggered again, but now both conditionA and conditionB are met -> condition A View Controller appears - as desired
somethingChanged triggered for the third time, now back to initial situation where only conditionB is met -> I return to the root View Controller (with Back button) instead of conditionB View Controller…
How can I keep going back and forth between these View Controllers?
Instead of the solution in the question you linked. Try this
init your own back button UIBarButtonItem as navigationBar's leftBarButtonItem in both conditionA View Controller and conditionB view controller
wire up with a action
- (IBAction)dismiss:(id)sender {
[self.navigationController popToRootViewControllerAnimated:YES];
}
I am developing an iOS application that uses a left side slide out drawer containing tabs, each representing one of the main views of the app. Currently, when the user selects a tab the application searches through the navigation stack for an instance of the relevant view controller and if it finds one pops back to that controller, otherwise it creates a new instance and pushes it onto the stack.
I would like to also add a back button allowing the user to go back to the previous view, however since many navigation options will pop the user to a previous view controller resulting in the controller they are leaving being dealloc'ed there is no obvious way to have a back button to get back to that controller again.
Is there any way to structure this application so that a back button can be added, while still allowing the user to use the tabs to navigate to any view at a given time?
An example of the navigation code follows (invoked when a user clicks one of the tabs):
if(![self.navigation.topViewController isKindOfClass:[GraphViewController class]]) { //Are we already in this view?
BOOL foundController = NO;
for(id controller in self.navigation.viewControllers) { //Is there a controller of this type already in the stack?
if([controller isKindOfClass:[GraphViewController class]]) {
[self.navigation popToViewController:controller animated:YES];
foundController = YES;
break;
}
}
if(!foundController) {
GraphViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"graphViewController"];
controller.connection = _connection;
controller.data = _dataCache;
[self.navigation pushViewController:controller animated:YES];
}
}
I believe what you want is navigation controllers for each item in the slide out menu. This way when the user selects a view from the side menu they can navigate the views associated with that section. This will allow the user to go back from a view once they have selected a item from the side menu.
I am using view controller containment, and as part of my implementation a child view controller needs to disable a bar button item of the navigation controller of the parent view controller. The theory is that being a child view controller, I had access to the same navigation controller (in my context anyway) as the parent view controller's.
From debugging I can see that self.navigationController and self.parentViewController.navigationController were set to the same address.
For example:
NSLog(#"%# - %#", self.navigationController,
self.parentViewController.navigationController);
NSLog(#"%# - %#", self.navigationItem,
self.parentViewController.navigationItem);
NSLog(#"%# - %#", self.navigationController.navigationItem,
self.parentViewController.navigationController.navigationItem);
Resulted in the following console log:
<UINavigationController: 0xc482290> - <UINavigationController: 0xc482290>
<UINavigationItem: 0xa5f3620> - <UINavigationItem: 0xc482490>
<UINavigationItem: 0xa5f36e0> - <UINavigationItem: 0xa5f36e0>
Results
Navigation controllers are the same, as expected (self->navController == self->parent->navController).
Navigation item's are different, this is expected. Each view controller has it's own nav item (self->navItem != self->parent->navItem).
Accessing the navigation controllers nav item from the parent or the child view controller is identical, as expected (self->navController->navItem == self->parent->navController->navItem)
So I now ask: why didn't disabling a bar button item in the child view controller with the following code work:
self.navigationController.navigationItem.rightBarButtonItem.enabled = NO;
This should be synonym to the following (which does work as expected) given their shared address:
self.parentViewController.navigationController.navigationItem.rightBarButtonItem.enabled = NO;
Update
I was reading my logs too fast. Indeed the parentViewController and the navigation controller have 2 different navigation items. I missed this in my example here because the memory addresses were extremely close: 0xa5f3620 != 0xa5f36e0
A UINavigationController has it's very own navigationItem (like any other UIViewController) that will only come into play if you push it onto the stack of another UINavigationController.
Generally you aren't going to push one UINavigationController onto another so manipulating self.navigationController.navigationItem is pointless.
EDIT:
To check ... I've just aded:
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
UINavigationItem *myNavControllersItem = self.navigationController.navigationItem;
UINavigationItem *myItem = self.navigationItem;
NSLog(#"%# - %#", myNavControllersItem, myItem);
}
to a VC that I'm pushing onto a NavController and get:
<UINavigationItem: 0x1ed0c170> - <UINavigationItem: 0x1ed46330>
Additionally, if I add:
self.navigationItem.rightBarButtonItem.enabled = NO; to viewDidAppear the right button gets disabled.
if I add:
self.navigationController.navigationItem.rightBarButtonItem.enabled = NO; it does not.
Well basically the problem is the way the navigation controller updates the bar button items, basically the buttons are update whenever the top-level child is changed.
For the right bar button item, if the new top-level child has it's own bar buttons items then the navigation controller will select the existing one, if not nothing will be displayed.
So in your case the parrentViewController already has a navigationItem.rightBarButonItem which is not disabled, by calling self.navigationController.navigationItem.rightBarButtonItem.enabled = NO; you are disabling the child's nabigationItem.rightBarButtonItem and you can't see this because the updates are made only when the top-level child is changed which is not your case.
So in order to disable the parentViewController right button you should do:
self.parentViewController.navigationItem.rightBarButtonItem.enabled = NO
For more details regarding the nav bar button updates please check apple docs regarding the Updating Navigation Bar Items