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)
}
}
Related
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];
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.
In my program have 6 view controllers.(in storyboard)
Lets define 1,2,3,4,5,6.
1 is my main view.
I want to navigate like this(image bellow).Is it possible to do?
give me a idea to do this navigation.
Yes, u can implement this. This is a simple navigation in iOS using NavigationController.
you have six viewControllers 1, 2, 3, 4, 5, 6.
to do this:
First create a NavigationController and initialize it with ViewController 1 (ie a root View Controller).
Now your navigationController behaves like a stack which contains all ur pushed view controller. NavigationController is only push and pop ur view controllers.
So, every time when u want to navigation first check ur viewController is inside navigationController stack or not. If it is already in stack then pop to that controller, if not then push the same view controller.
for this use following:
In case ViewController3
-(void)popToSelectedViewController
{
NSArray *vc=[self.navigationController viewControllers];
ViewController3 *vc3=nil;
for (int i=0; i<[vc count]; i++)
{
UIViewController *tempVC=[vc objectAtIndex:i];
if([tempVC isKindOfClass:[ViewController3 class]])
{
vc=[vc objectAtIndex:i];
break;
}
}
if(vc3)
{
//If exists inside stack the pop
[self.navigationController popToViewController:vc3 animated:YES];
}
else
{
//If not exists inside stack push ViewController3
ViewController3 *vc3New= [[ViewController3 alloc]initWithNibName:#"ViewController3" bundle:nil];
[self.navigationController pushViewController:vc3New animated:YES];
[vc3New release];
}
}
For initializing ur ViewController1 with navigationController:
if using storyboard
Embed ur initialViewController(ie viewController3) with UINavigationController.
for this:
Step1: open storyboard, and select ur initialViewController(ie viewController3).
Step2: Go to Editor in menu -> Choose Embed In -> Select UINavigationController.
this creates a navigationcontroller and initializes with viewController3 as rootViewController.
if not using storyboard
make property of vc3 (ViewController3) and applicationNavigationController (UINavigationController) in .h
and in .m:
got method "application... didFinishedLaunching...." in appDelegate
and write:
self.vc3=[[ViewController3 alloc]initWithNibName:#"ViewController3" bundle:nil];
self.applicationNavigationController=[[UINavigationController alloc] initWithRootViewController:self.vc3];
self.window.rootViewController=self.applicationNavigationController;
[self.window makeKeyAndVisible];
First You create a navigation controller object
UINavigationController *navCtrl = [[UINavigationController alloc]initWithRootViewController:rootViewController];
self.window.rootViewController = navCtrl;
If you want to go to 1->2,1->3,1->6,etc, create an object for the next viewcontroller and push it to navigation stack
[self.navigationController pushViewController:second animated:YES];
You dont need to do any additional work to go back to the previous view controller. The default back button lets you go back.
If you need to return to the root view controller, then use this:
[self.navigationController popToRootViewControllerAnimated:YES];
if you want to return to any particular view controller, then use this
[self.navigationController popToViewController:viewController animated:YES];
Use UINavigationController.It is nor circular.It can be assumed as stacked approch.That is what navigation controller do
A must read for you
I need to jump between view controllers. For example:
View1: First screen (Just logo)
View2: Download Screen
View3: First app screen (Some Buttons)
View4-View(N): some app screens
When user enters app the app goes to View1->View2 (downloads stuff)->View 3->View4->View5
Then user wish to go to First app screen (View3) he does:
NSArray *array = [self.navigationController viewControllers];
[self.navigationController popToViewController:[array objectAtIndex:2] animated:NO];
The first time user enters the app it goes: View1->View3 (The download screen no longer needed), and I have different push segue to go to View3 so lets assume the user goes to: View1->View 3->View4->View5, now he wishes to go back to View3, so the function:
NSArray *array = [self.navigationController viewControllers];
[self.navigationController popToViewController:[array objectAtIndex:2] animated:NO];
Will return him to View4, and this is WRONG. How can I solve it?
Well if you are using storyboards , what you can do is set your uiviewcontrollers' storyboard id and use it for popping and pushing your views.
Lets say your Storyboards name is MainStoryboard.storyboard
UIStoryboard* sb = [UIStoryboard storyboardWithName:#"MainStoryboard"
bundle:nil];
SettingsListViewController *settingsVC = [sb instantiateViewControllerWithIdentifier:#"SettingsListViewController"]; // #"SettingsListViewController" is the string you have set in above picture
[self.navigationController popToViewController:settingsVC animated:YES];
Above solution should work for you , but If I were you I would change the structure of my app , you say :
View1: First screen (Just logo)
View2: Download Screen
Since View1 is just logo and View 2 is also a view that you skip, you can remove them from navigation controller and make View3 your navigation controller's root view controller and when you need view1 and view2 present them as Modal View Controllers,
when you are done with them lets say; user successfully loaded the app dismiss logo screen and present Download Screen if download successful then dismiss it.
So your View3 Will be there as root view controller, lets say your at View(n) you want to go to home screen which is View3 all you need to do is call
[self.navigationController popToRootViewControllerAnimated:NO];
When you are on view(n) and want to pop to view(n-1) just use
[self.navigationController popViewControllerAnimated:YES];
good luck,
I always use this, and it will work fine in your case. In fact the following line of code is copied to my "Notes" for quick copy/paste.
Be sure to import your ViewController.h file, if not.
for (UIViewController *viewController in self.navigationController.viewControllers) {
if([viewController isKindOfClass:["your ViewController" class]]) {
[self.navigationController popToViewController:viewController animated:NO];
}
}
In the second sequence your navigation stack changes and view3 would be at index 1. so
[[self.navigationController popToViewController:[array objectAtIndex:1] animated:NO];
would be the right way of doing it.
according to your sitation use directly name of viewController
create instance of your viewController,like this
i supposed here that your viewController name is-view3Controller
View3Controller view3Controller=[[View3Controller alloc]init];
[self.navigationController popToViewController:#"view3Controller" animated:NO]
or if you are using storyboard then
View3Controller view3Controller=[self.storyboard instantiateViewControllerWithIdentifier:#"view3Controller"];
[self.navigationController popToViewController:#"view3Controller" animated:NO]
It sounds like View 2 is not being added to your view controllers array at run time, possibly because of the segue you've created.
Try removing the segue that transitions from View 1 > View 3 and pushing the user past View 2 without animating, as your application's logic requires it:
// If the user needs to skip ahead to view 3, conditionally push view 2 and view 3 without animating
[self.navigationController pushViewController:viewController2 animated:NO];
[self.navigationController pushViewController:viewController3 animated:NO];
Alternatively if you left the segue in place could you not look at the size of the UINavigationController viewControllers property and "guess" based on the size whether or not you skipped View 2? If you did then you can adjust the popToViewController method to pop to the correct index. This is, admittedly less elegant and brittle if you need to skip other views as well.
// Check length of viewController array with 'N' views (pseudo code)
if (self.navigationController.viewControllers.length == N-1)
// View 2 was ignored: pop to objectAtIndex:1
else
// View 2 was included: pop to objectAtIndex:2
If I understood you correctly, your view3 has special view controller, so you can use such code:
NSArray *VCs = [self.navigationController viewControllers];
for (UIViewController *VC in VCs)
{
if ([VC isKindOfClass:[**YOUR-VIEW-CONTROLLER** class]]) {
[self.navigationController popToViewController:VC animated:NO];
}
}
It's simple and it works!
for (UIViewController* controller in self.navigationController.viewControllers) {
if ([controller isKindOfClass:[<Your View Controller Name> class]]) {
[self.navigationController popToViewController:controller animated:YES];
return;
}
}
Go like this to a particular view controller
[self.navigationController popToViewController:[[self.navigationController viewControllers]objectAtIndex:1] animated:YES];
I am writing a simple application that has 3 view controllers. The root view controller is an item listing, basic table view. Off of this view controller, I push two different view controllers based on some user interaction - a create item view controller or a view item view controller.
So, the storyboard segues just look like a V, or something.
On my create item view controller, I would like it to pop back to the root view controller when the user creates a new item, but then push to the view item controller so that I can look at the newly created item.
I can't seem to get this to work. It's easy enough to pop back to the root view controller, but I'm unable to push that view item controller.
Any ideas? I've pasted my code, below. The pop function works, but the new view never appears.
- (void) onSave:(id)sender {
CLLocation *currentLocation = [[LocationHelper sharedInstance] currentLocation];
// format the thread object dictionary
NSArray* location = #[ #(currentLocation.coordinate.latitude), #(currentLocation.coordinate.longitude) ];
NSDictionary* thread = #{ #"title": _titleField.text, #"text": _textField.text, #"author": #"mustached-bear", #"location": location };
// send the new thread to the api server
[[DerpHipsterAPIClient sharedClient] postPath:#"/api/thread"
parameters:thread
success:^(AFHTTPRequestOperation *operation, id responseObject) {
// init thread object
Thread *thread = [[Thread alloc] initWithDictionary:responseObject];
// init view thread controller
ThreadViewController *viewThreadController = [[ThreadViewController alloc] init];
viewThreadController.thread = thread;
[self.navigationController popToRootViewControllerAnimated:NO];
[self.navigationController pushViewController:viewThreadController animated:YES];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[self.navigationController popToRootViewControllerAnimated:YES];
}];
}
If I understand you correctly, you have a stack of view controllers:
A (root) - B - C - D - E
And you want it to become:
A (root) - F
Right? In that case:
NSArray *viewControllers = self.navigationController.viewControllers;
NSMutableArray *newViewControllers = [NSMutableArray array];
// preserve the root view controller
[newViewControllers addObject:[viewControllers objectAtIndex:0]];
// add the new view controller
[newViewControllers addObject:viewThreadController];
// animatedly change the navigation stack
[self.navigationController setViewControllers:newViewControllers animated:YES];
Swift 4
// get current view controllers in stack and replace them
let viewControllers = self.navigationController!.viewControllers
let newViewControllers = NSMutableArray()
// preserve the root view controller
newViewControllers.add(viewControllers[0])
// add the new view controller
newViewControllers.add(viewThreadController)
// animatedly change the navigation stack
self.navigationController?.setViewControllers(newViewControllers as! [UIViewController], animated: true)
I think
[self.navigationController pushViewController:viewThreadController animated:YES];
is using a different NavigationController than the statement before that.
Because after popping to the root view Controller you loose the navigation Controller you are in. Solve that using this code instead
UINavigationController *nav = self.navigationController;
[self.navigationController popToRootViewControllerAnimated:NO];
[nav pushViewController:viewThreadController animated:YES];
I also think that this wont solve your whole problem. You will probably get an error saying that two fast popping and pushing may invalidate the NavigationController.
And to solve that you can either push the NavigationController in the viewDidDissappear Method of the 2nd View Controller or push it in the viewDidAppear Method in the Main View Controller(item listing).
An easy way to accomplish what you want to do is to build some simple logic into your main root view controllers -(void)viewWillAppear method and use a delegate callback to flip the logic switch. basically a "back reference" to the root controller. here is a quick example.
main root controller (consider this controller a) - well call it controllerA
set a property to keep track of the jump status
#property (nonatomic) BOOL jumpNeeded;
setup some logic in
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.jumpNeeded ? NSLog(#"jump needed") : NSLog(#"no jump needed");
if (self.jumpNeeded) {
NSLog(#"jumping");
self.jumpNeeded = NO;
[self performSegueWithIdentifier:#"controllerC" sender:self];
}
}
Now, in your main root controller,when a tableview row is selected do something like this
when pushing to controllerB in your tableView did select method
[self performSegueWithIdentifer#"controllerB" sender:self];
then implement your prepare for segue method
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
//setup controller B
if([segue.identifier isEqualTo:#"controllerB"]){
ControllerB *b = segue.destinationViewController;
b.delegate = self; //note this is the back reference
}
//implement controller c here if needed
}
Now move on to controllerB
you need to set a property called "delegate" to hold the back reference
and you need to import the header file from the root controller
#import "controllerA"
#property (nonatomic,weak) controllerA *delegate;
then just before you pop back to controllerA, you set the flag
self.delegate.jumpNeeded = YES;
[self.navigationController popViewControllerAnimated:YES];
and that is about it. You don't have to do anything with controllerC. There are a few other ways to do, but this is pretty straight forward for your needs. hope it works out for you.
Sharing a category on UINavigationController based on Dave DeLong's answer that we use in our application to keep the back button always working as required.
#implementation UINavigationController (PushNewAndClearNavigationStackToParent)
- (void) pushNewControllerAndClearStackToParent:(UIViewController*)newCont animated:(BOOL)animated
{
NSArray *viewControllers = self.viewControllers;
NSMutableArray *newViewControllers = [NSMutableArray array];
// preserve the root view controller
[newViewControllers addObject:[viewControllers firstObject]];
// add the new view controller
[newViewControllers addObject:newCont];
// animatedly change the navigation stack
[self setViewControllers:newViewControllers animated:animated];
}
#end
NSArray *viewControllers = self.navigationController.viewControllers;
NSMutableArray *newViewControllers = [NSMutableArray array];
// preserve the root view controller
for(int i = 0; i < [viewControllers count];i++){
if(i != [viewControllers count]-1)
[newViewControllers addObject:[viewControllers objectAtIndex:i]];
}
// add the new view controller
[newViewControllers addObject:conversationViewController];
// animatedly change the navigation stack
[self.navigationController setViewControllers:newViewControllers animated:YES];
I don't think this is possible because popping back will deallocate everything.
I think a better way is to put your data on a model singleton class, pop to the rootviewcontroller and listen for the pop to end. Then you check if there is some data stored on the model so that you know if you should push a new viewcontroller.
I used Dave's answer as inspiration and made this for Swift:
let newViewControllers = NSMutableArray()
newViewControllers.addObject(HomeViewController())
newViewControllers.addObject(GroupViewController())
let swiftArray = NSArray(array:newViewControllers) as! Array<UIViewController>
self.navigationController?.setViewControllers(swiftArray, animated: true)
Replace HomeViewController() with whatever you want your bottom view controller to be
Replace GroupViewController() with whatever you want your top view controller to be
If what you want to do is navigate in any way inside the same navigation controller... you can access the navigationStack A.K.A. viewControllers and then set the viewcontrollers in the order that you want, or add or remove some...
This is the cleanest and native way to do it.
UINavigationController* theNav = viewControllerToDismiss.navigationController;
UIViewController* theNewController = [UIViewController new];
UIViewController* rootView = theNav.viewControllers[0];
[theNav setViewControllers:#[
rootView,
theNewController
] animated:YES];
do any necessary validations so you don't get any exception.