iOS – Showing a view controller already in the navigation stack - ios

This is probably easily sorted but I can't figure it out. I have a tab bar application with two tabs, each tab has a UINavigationController.
Let's say that I in tab 1 push a viewcontroller called ItemViewController, then I go to tab 2. From tab 2 I want to programatically display the ItemViewController. So it should first take me to tab 1 and then display the ItemViewController.
This is easily achievable by just tapping on the tab 1 tab item but I want to do this programtically for other reasons.
What I'm doing right now to achieve this:
[tab1NavController popToRootViewControllerAnimated:NO];
[tabBarController setSelectedIndex:0];
[tab1NavController pushViewController:itemViewController animated:NO];
I would like to be able to do something like this in pseudo-code:
if(viewControllerIWantToDisplayIsOnNavStack)
[tab1NavController presentViewController:viewControllerIWantToDisplay];
else
//instantiate and pushviewcontroller onto stack
How can I achieve this?

// check if the desired controller is on the stack in tab 1
if([[tab1NavController viewControllers] containsObject:viewControllerIWantToDisplay]) {
// desired controller is on the stack, let's see if it's on top
if(tab1NavController.topViewController == viewControllerIWantToDisplay) {
// no need to do anything
}
else {
// we need to pop to the desired view controller
[tab1NavController popToViewController:viewControllerIWantToDisplay animated:NO];
}
} else {
// desired controller not on the stack
[tab1NavController pushViewController:viewControllerIWantToDisplay animated:NO];
}
So you don't have to pop to root view controller in the tab 1 any more.

for me following way worked, i used loop to check if class is in stack array
NSInteger viewControllersCount = 0;
UIViewController *rootViewController = nil;
Yourcontroller *viewController = nil;
NSArray *viewControllers = self.navigationController.viewControllers;
viewControllersCount = viewControllers.count - 1;
for (int i = viewControllersCount ; i > 0 ; i--) {
rootViewController = [viewControllers objectAtIndex:i];
if([rootViewController isKindOfClass:[Yourcontroller class]])
{
viewController = (NWAAccountViewC *)rootViewController;
[self.navigationController popToViewController:rootViewController animated:YES];
break;
}
}

it's very easy to programmatically select another tab, just do this:
tabbarcontroller.selectedIndex = tabNr;
But I see you already know this. You can check whether the top view controller on the stack is the type of viewcontroller you need by checking like this:
if([navigationcontroller.topviewcontroller isKindOfClass:[Yourcontroller class]])
{
//change tabbar or something else
}

Related

Is there any way that I can navigate to Viewcontroller which is middle of the UInavigationcontroller stack

I have Collection View Controller PatientList -> on selection of cell navigates to PatientdetailView -> on click of button navigates to startDignosisView. This is Navigation controller stack. Now from Patient List I have button "ADD" that navigates to AddpatientView, from where I have to navigate to StartdignosisView without disturbing Navigation stack. How can I do it?
for (UIViewController *controller in self.navigationController.viewControllers)
{
if ([controller isKindOfClass:[NeededViewController class]])
{
[self.navigationController popToViewController:controller
animated:YES];
break;
}
}
if let viewControllers = self.navigationController?.viewControllers {
var newStack: [UIViewController] = []
for viewController in viewControllers {
newStack.append(viewController)
if viewController is StartdignosisView {
break
}
}
self.navigationController?.viewControllers = newStack
}
try like this:
some *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"some identifier you add to a vc in the storyboard"];
[self.navigationController pushViewController:vc animated:YES];
Just set the identifier to the storyboard for that view controller.
If you are not using storyboard then follow the below steps:
See if you Add Patient and then directly navigate to StartDignosisVC then you will not be able to pop to PatientDetailVC
You can do any one from these two:
First:
Try to present your AddPatientVC, add new and then dismiss it and follow your old stack path.
Second:
One ADD button action instantiate your AddpatientView like this:
AddPatientVC *obj = [storyboard instantiateViewControllerWithIdentifier:#"AddPatientVC"];
And similarly navigate to StartdignosisView with setting a flag that you are coming from AddPatientVC:
StartDignosisVC *obj = [storyboard instantiateViewControllerWithIdentifier:#"StartDignosisVC"];
obj.isFromAddPatientView = true
When you press Back button from StartDignosisVC then check if you came from AddPatientVC to directly pop it to Patient list.
If you are using storyboard then you can do the following see attached screen shot:
Thanks Mayank for your answer: following code worked in my case.
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:0] animated:YES];

Presenting a new view controller without infinitely stacking them?

I am working on an app where it is kind of like a website. There is a menu that every viewcontroller has in it and that needs to switch to the correct view.
There is no concept of back in my application. Thus, if I use UINavigation to push a view, I end up stacking up lots of views.
Same problem if I add the vc as a child or present it, you end up with hundreds of vcs after a while.
What is a way I can design this to safely have only 1 view at any given time?
Thanks
Like this.
Add this to all your controllers that have access to the navigation controller. When you want to push a new controller, instead of using pushViewController:animated you can use this. You can modify the code to take animated as a parameter aswell.
- (void)pushIfNotInStack:(UIViewController*)viewController
{
BOOL isInStack = NO;
NSMutableArray * vcStack = [self.navigationController.viewControllers mutableCopy];
for (NSInteger i = 1 ; i <vcStack.count; i++){
if ([viewController isKindOfClass:[[vcStack objectAtIndex:i]class]]) {
[vcStack replaceObjectAtIndex:i withObject:viewController];
isInStack = YES;
}
}
if(isInStack){
[self.navigationController setViewControllers:vcStack];
[self.navigationController popToViewController:viewController animated:YES];
} else {
[self.navigationController pushViewController:viewController animated:YES];
}
}
Use a tab bar view controller and hide the tab bar.
A tab view controller can have a number of children view controllers. You can add these children view controllers in storyboard or in code.
You can use your menu to choose which child view controller you want to display. The children view controllers are indexed from 0 to n - 1, where n is the number of view controllers. If you want to display the view controller at index i:
[self.tabBarController setSelectedIndex:i];
Don't forget to hide your tab bar in each child view controller's viewDidLoad
You can also sublass your UINavigationController class and override pushViewController:animated: method as follow:
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
if (self.viewControllers.count > <#the number of controllers you want to keep in memory eg. 20 #>) {
NSMutableArray *mutableViewControllers = [self.viewControllers mutableCopy];
//remove 2 last controllers in controller stack (from the bottom)
[mutableViewControllers removeObjectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]];
self.viewControllers = [mutableViewControllers copy];
}
[super pushViewController:viewController animated:animated];
}
This approach gurantees you to alloc init controllers in normal way and don't bother about they pointers to memory.

How do I manually set the "Back" destination in iOS apps

I have a UIViewController called 'detailViewController'.
This view controller is accessed through multiple different segues using the push segue.
The problem I am facing is that the back button on the detailViewController takes the user back to the previous view controller (as it should), however I would like the back button to take the user to the masterViewController (the default UIViewController in the app).
How do I do this?
I have tried changing the segue types however that didn't really do anything at all.
Peter
The method you're looking for is UINavigationController's popToRootViewControllerAnimated:
From the documentation: "Pops all the view controllers on the stack except the root view controller and updates the display."
You'll need to create a custom back button. You can't afaik override the back button's functionality.
So something like:
UIButton *myBackButton = [UIButton buttonWithType:UIButtonTypeCustom];
[myBackButton addTarget:self action:#selector(popToRoot:) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *myCustomBackButtonItem = [[UIBarButtonItem alloc] initWithCustomView:myBackButton];
[self.navigationItem setLeftBarButtonItem:myCustomBackButtonItem];
and then the implementation of popToRoot: would look like:
- (void)popToRoot:(id)sender
{
[self.navigationController popToRootViewControllerAnimated:YES];
}
If the masterViewController you're talking about is the root you can call:
[self.navigationController popToRootViewControllerAnimated:YES];
You can create the back button yourself and call [self.navigationController popToRootViewControllerAnimated:YES]. However, that does require you to create the back button yourself. An alternative option could be to take the current view controller, and remove all of them except the first and current ones:
NSMutableArray *viewControllers = [self.navigationController.viewControllers mutableCopy];
[viewControllers removeObjectsInRange:NSRangeMake(1, [viewControllers count] - 2)];
self.navigationController.viewControllers = viewControllers;
Use the following code in order to go back to main view controller:
[self.navigationController popViewControllerAnimated:YES];
Edit:
Based on the comments, I realize that my code takes you only one step back and may not answer what is asked in this question. Thanks.
I came up with a workaround that looks up in a navigation controller's collection and find the offset between the existing and destination controller using name of destination controller and remove the in-between controller. Not sure if it is close to your need but give it a try:
- (NSArray *)navigateToViewController:(NSString *)destinationControllerName withControllers:(NSArray *)sourceControllers
{
NSMutableArray *controllers = [[NSMutableArray alloc] initWithArray:sourceControllers];
NSInteger sourceControllerIndex = [controllers count] - 1; // Value should be index oriented instead of position oriented.
NSInteger destControllerIndex = 0;
// Find position of destination controller (index oriented)
for (UIViewController *controller in controllers)
{
NSString *controllerName = NSStringFromClass([controller class]);
NSLog(#"class name: %#", controllerName);
if([[controllerName lowercaseString] isEqualToString:[destinationControllerName lowercaseString]])
break;
destControllerIndex +=1;
}
// If desired controller does not exists in the stack, do not proceed.
if (destControllerIndex == sourceControllerIndex)
{
return controllers;
}
// If destination is the same as source do not enter in this block
// If destination and source controllers have zero or no controllers in between do not enter in this block
// If destination and source controllers has few controllers (at least one) in between, go inside this block.
// Here "destControllerIndex + 1" shows, there is at least one controller in between source and destination.
else if(destControllerIndex + 1 < sourceControllerIndex)
{
NSLog(#"controllers in stack: %#", controllers);
// Start from the controller following the source controller in stack and remove it
// till the destination controller arrives
for (NSInteger iterator = sourceControllerIndex - 1; iterator > destControllerIndex; iterator--)
{
UIViewController *cont = [controllers objectAtIndex:iterator];
if(cont) {
[cont release]; cont = nil; }
[controllers removeObjectAtIndex:iterator];
NSLog(#"controllers in stack: %#", controllers);
}
}
return controllers;
}
and define it in some base controller:
-(void) popToViewController:(NSString *)controllerName withNavController:(UINavigationController *)navController
{
// STStatistics refers a singleton class of common functions.
NSArray *mArr = [STStatistics navigateToViewController:controllerName withControllers:navController.viewControllers];
navController.viewControllers = mArr;
// There should be more than one controller in stack to pop.
if([mArr count] > 1)
{
[navController popViewControllerAnimated:YES];
}
}
and here is how called:
[self popToViewController:#"NameOfDestinationControllerInNavStack" withControllers:self.navigationController];

Memory increasing on navigation, never decreasing. How can I decrease when view controller is popped?

In my program I have six view controllers. To navigate among these view controllers I used UINavigationController.
I will explain this issue using two view controllers.
In first view controller, I implemented a button action like this
- (IBAction)clickSearch:(id)sender{
[self gotoSearch];
}
-(void)gotoSearch
{
NSArray *vc = [self.navigationController viewControllers];
ViewControllerSearch *vcSearch = nil;
for (int i = 0; i < [vc count]; i++)
{
UIViewController *tempVC = [vc objectAtIndex:i];
if ([tempVC isKindOfClass:[ViewControllerSearch class]])
{
vcSearch = [vc objectAtIndex:i];
break;
}
}
if (vcSearch)
{
//If exists inside stack the pop
[self.navigationController popToViewController:vcSearch animated:YES];
}
else
{
//If not exists inside stack push ViewController3
ViewControllerSearch *vc3New = [self.storyboard instantiateViewControllerWithIdentifier:#"vcsearch"];
[self.navigationController pushViewController:vc3New animated:YES];
vc3New = nil;
}
}
second view controller has a button and that button action is also implemented as above.
if I move to second view controller from first view controller ,memory increasing.(its ok)
if I move to first view controller from second view controller, memory level not changing.
again I move to second view controller from first view controller ,memory increasing.you can see it in this image.
(memory is not reducing)How can I reduced memory?
first time first to second : NSArray *vc has one object (first VC)
second to first : NSArray *vc has two object (first VC,and second VC)
now im in first view controller.now im click to move second view controller : NSArray *vc has only one object (first VC).second vc missing.and increasing memory (i think it for new second vc)

How to go 2 views back in navigation based application

I have created a View based based application of 4 views. By using navigation controller I am changing the view. In my 3rd view one button is there. If I click on that button the app should come to the first view (2 views back).
I have used
[self.navigationController popViewControllerAnimated:YES];
[self.navigationController popViewControllerAnimated:YES];
This is not working. It's going to the previous page only.
for (UIViewController *controller in self.navigationController.viewControllers)
{
if ([controller isKindOfClass:[NeededViewController class]])
{
[self.navigationController popToViewController:controller
animated:YES];
break;
}
}
Try out this, and make change according your specification
-(void)goToMainCategoryView;
{
id object = nil;
for (UIViewController *viewControl in self.navigationController.viewControllers)
{
if(viewControl.view.tag == 0)
{
object = viewControl;
}
}
[self.navigationController popToViewController:object animated:YES];
}
Another easy root to select the UIViewController by index would be to use:
NSArray *viewsArray = [self.navigationController viewControllers];
UIViewController *chosenView = [viewsArray objectAtIndex:1];
[self.navigationController popToViewController:chosenView animated:YES];
chosenView would then be the second view in the navigation stack (position 1). If you had a large stack and wanted to go a specific view.
Use
popToRootViewControllerAnimated:
to go all the way back to the top view controller:
Documentation:
Pops all the view controllers on the stack except the root view controller and updates the display.
- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated
or
popToViewController:animated:
to go back to a particular view controller, supply the view controller you want to go to.
Documentation:
Pops view controllers until the specified view controller is at the top of the navigation stack.
- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated
Parameters
viewController
The view controller that you want to be at the top of the stack.
The same thing in swift 1.2 :: xcode:6.4
for controller: UIViewController in self.navigationController?.viewControllers as! [UIViewController] {
if controller.isKindOfClass(YourViewController) {
self.navigationController!.popToViewController(controller, animated: true)
}
}

Resources