I am building an application in which, i have 3 ViewControllers.
Also i have created custom navigation controller.
A-ViewController -> This contains 2 buttons, 1st button is for opening B-ViewController & 2nd button is for C-ViewController.
From 1st button, I am using pushviewcontroller for B-ViewController, means B-ViewController is pushed from A-ViewController.
From 2nd button C-ViewController is presented using presentviewcontroller.
Now on pressing back button in both B & C ViewControllers, I have to use pop view controller in B-ViewController & dismiss in C-ViewController.
As currently i know the pages, but there should be a generic solution.
Is there any way to identify whether current navigation controller is pushed or popped.
As there are some pages which can be pushed or presented, but i dont want to set any bool variable. I need use the support from apple.
NSArray *arrViewControllers = [[AppDelegate sharedInstance].navigationController viewControllers];
NSLog(#"[arrViewControllers count] = %d",[arrViewControllers count]);
I am using the above code for fetching the list of view controllers in the stack.
But i am not able to identify whether it is pushed or presented.
Can anybody help me in this ?
You can check if your view controller exists in the navigation view controller's stack. Simply check it as
if([self.navigationController topViewController] == self){
//VC is the top most view controller
[self.navigationController popViewControllerAnimated:YES];
}else{
//You can put some checks here to be dead sure its a modally presented view controller
[self dismissViewControllerAnimated:YES completion:nil];
}
This is the code for Finding the navigation controller is presented or pushed from previous controller.
NSArray *arrViewControllers = [[AppDelegate sharedInstance].navigationController viewControllers];
UIViewController *viewController = [arrViewControllers lastObject];
if (viewController.presentedViewController)
{
[self dismissViewControllerAnimated:YES completion:nil];
}
else{
[self.navigationController popViewControllerAnimated:YES];
}
Related
WLINewPostViewController *newPostViewController = [[WLINewPostViewController alloc] initWithNibName:#"WLINewPostViewController" bundle:nil];
UINavigationController *newPostNavigationController = [[UINavigationController alloc] initWithRootViewController:newPostViewController];
newPostNavigationController.navigationBar.translucent = NO;
[tabBarController presentViewController:newPostNavigationController animated:YES completion:nil];
So I just simply push a new UIViewController.
Then after it posts the server callback calls a method with this code from the WLINewPostViewController.m:
[self dismissViewControllerAnimated:YES completion:^{
NSLog(#"Completed");
}];
[[self navigationController] popViewControllerAnimated:YES];
if (self == self.navigationController.visibleViewController){
NSLog(#"self = visibile");
}
if (self == self.presentingViewController.presentingViewController){
NSLog(#"self = presenting");
}
}
I tried a bunch of different things and none work.
I am relatively new to Xcode but after trying
[self dismissViewControllerAnimated:YES completion]
[self.navigationController popViewControllerAnimated:YES]
[self.navigationController.visibleViewController.presentedViewController dismissViewControllerAnimated:YES completion:nil];
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
and every other possibility, I am officially stumped. The WLINewPostViewController still won't dismiss.
It Logs out "self = visible"
Let me illustrate what you are trying to do
You have a navigation controller with Controller A.
Here you are trying to present another Controller B from Controller A.
Now when you get a callback from the server, you should call dismissViewControllerAnimated from Controller B to dismiss itself.
So after dismissViewControllerAnimated:completion: method call, the Controller B will be dismissed and Controller A will be shown automatically. Now you do not need to call popViewControllerAnimated: in completion block again as there is no other Controller in navigation controller to load.
If you have different use case, let me know I can provide solution.
You are presenting a view over navigationbar instead of pushing it over navigationbar.
When push you pop. When you present you dismiss. So instead of popViewControllerAnimated you need to use dismissViewControllerAnimated:completion
dismiss behaves differently depending on the receiver. From the docs:
The presenting view controller is responsible for dismissing the view controller it presented. If you call this method on the presented view controller itself, it automatically forwards the message to the presenting view controller.
If you present several view controllers in succession, thus building a stack of presented view controllers, calling this method on a view controller lower in the stack dismisses its immediate child view controller and all view controllers above that child on the stack. When this happens, only the top-most view is dismissed in an animated fashion; any intermediate view controllers are simply removed from the stack. The top-most view is dismissed using its modal transition style, which may differ from the styles used by other view controllers lower in the stack.
In short, if the vc on top calls it on itself, it dismisses itself. Anywhere else on the stack dismisses to that point, animating only the topmost vc.
What's extra confusing (for you and many others) is that the navigation vc has a stack too, and your problem is complicated further by presenting an navigation vc atop a tab-bar vc.
So what to do? The question is unclear about which vc is the receiver in the posted code (who is self in that snippet?). The text implies that self is a vc on the stack of the presented navigation vc, like...
TabBarVC --- presents ---> NavVC
| |
| --- viewControllers stack = rootVC, vc1
|
---> viewControllers for each tab
... and it's root or vc1 that wants to dismiss. If I'm right about that, then, given the docs, the solution is clear:
[self.navigationController dismissViewControllerAnimated:YES completion:^{}];
will put us back on the tabbar vc on whatever tab was visible when we did the present.
In android, switching between activities, is fairly straightforward
you call
Intent intent = new Intent(this,NextActivity.class); <- define the next activity
startActivity(intent); <- start the next activity
finish(); < -get rid of the current activity
now in iOS i know how to do this:
UIViewController *nextviewcontroller = [[UIViewController alloc]initWithNibName:#"nextvc" bundle:nil];
[self presentViewcontroller:nextviewcontroller animated:YES completion:nil];
How do I get rid of the current view controller? so that currentviewcontroller dies after presenting nextviewcontroller ?
[self dismissViewController:YES]; doesnt seem to do the trick
the lifecycle methods viewWillDisappear and viewDidDisappear are called even if I don't call [self dismissViewController:YES];
i want "currentviewcontroller" to be removed from the memory, and from the viewcontroller stack, so that clicking "back" in "nextviewcontroller" will go to some thirdviewcontroller that was before currentviewcontroller
In iOS is different, since there's no concept of Activity and everything is more focused on the app itself (in Android you can mix activities from different apps). Therefore, there's no concept of "view controller stack".
The most similar concept is the "navigation stack" of navigation controllers, where you actually push and pop new view controller into some kind of linear navigation. A navigation bar is automatically created and populated with back buttons.
presentViewController will show your view controller modally upon the current one, but you can't thrash the presenting one since it's holding and containing ("defining context") the new one.
If you use a navigation controller for your navigation hierarchy (I don't know if you can), you can override the back button and use something like
UIViewController * prev = self.navigationController.viewControllers[self.navigationController.viewControllers.count -2 ]
[self.navigationController popToViewController:prev animated:YES]
With a modal view controller, you may try something like (I haven't tried but it may work)
[self.presentingViewController.navigationController popViewControllerAnimated:YES]
You should write one of these code into the target action of your close button.
iOS doesn't maintain a global stack of controllers in the way that Android does. Each app shows a controller at its root, and that one is responsible for showing the other controllers in the app. Controllers can display other controllers modally using presentViewcontroller:animated:completion: but the presenting controller remains underneath the presented one.
If your current controller is the root controller, then instead of using presentViewcontroller:animated:completion: you'd just do this:
self.view.window.rootViewController = nextViewController;
It's very common for the root controller to be a UINavigationController, which does manage a stack of controllers. If that is the case, and if your current controller is at the top of the stack, you'd do this:
[self.navigationController popViewControllerAnimated:NO];
[self.navigationController pushViewController:nextViewController animated:YES];
If your setup is different, you'd do something different; it's hard to say what without knowing more. But it's most likely that you'd be in the UINavigationController case.
In the viewDidAppear of your nextviewcontroller you could add :
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
NSArray *controllers = self.navigationController.viewControllers;
NSMutableArray *newViewControllers = [NSMutableArray arrayWithArray:controllers];
[newViewControllers removeObjectAtIndex:[controllers count]-2];
self.navigationController.viewControllers = newViewControllers;
}
There is nothing available like this in iOS but you can achieve it doing something like below
NSArray *viewControllers=[self.navigationController viewControllers];
NSMutableArray *newControllers=[[NSMutableArray alloc] init];
for(int i=[viewControllers indexOfObject:self];i<viewControllers.count;i++){
[newControllers addObject:[viewControllers objectAtIndex:i]];
}
[self.navigationController setViewControllers:[[NSArray alloc] initWithArray:newControllers]];
I have tried the method of storing all the view controllers in an array but it didn't work for me . When you try popViewController it will move to the View Controller which is last in the stack.
You can make 2 navigation controllers and switch between them and also switch between the view controllers of a particular Navigation Controller.
For eg.
You can switch between 2 Navigation Controller using the following code:
FirstNavController *fisrtView=[storyboard instantiateViewControllerWithIdentifier:#"firstnavcontroller"];
self.window.rootViewController = firstView;
}else{
SecondNavController *secondView=[storyboard instantiateViewControllerWithIdentifier:#"loginnavcontroller"];
self.window.rootViewController = secondView;
}
If your FirstNavController has 2 ViewControllers then you can switch between them using pushViewController
SecondViewController *sc = [self.storyboard instantiateViewControllerWithIdentifier:#"secondviewcontroller"];
[[self navigationController] pushViewController:sc animated:YES];
and popViewController
[self.navigationController popViewControllerAnimated:YES];
Here is my problem. I had a UITableViewcontroller to which UINavigation controller is Embedded. To UITableviewController I had Add screen and an edit screen. Add screen is working perfectly. When I click on the records on the table view cell it is able to redirect to the edit page (detailed view). When I hit on Submit button the page is not navigating to the tableview cell. Here is the below error.
Warning: Attempt to present on whose view is not in the window hierarchy!
Here is the code I was trying for navigation.
UINavigationController *questionnaireNavController = [self.storyboard instantiateViewControllerWithIdentifier:#"DLProjectsTasksubtasks"];
[questionnaireNavController setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
DLProjectsTasksubtasks *qvc = (DLProjectsTasksubtasks *)[questionnaireNavController topViewController];
[qvc.tableView reloadData];
[self presentViewController:questionnaireNavController animated:YES completion:nil];
[self dismissViewControllerAnimated:YES completion:nil];
Between I was using a Segue for transfering the data from tableview data to Edit Screen
I think your segue is being pushed on the navigation controller rather than being presented modally. So you have two options, either change the segue in your storyboard to be modal and use the dismissViewControllerAnimated:YES completion: or keep it the way it is and use popViewControllerAnimated: on your navigation controller.
[self.navigationController popViewControllerAnimated:YES];
For passing the data to the previous page you can either pass a reference of the previous view controller OR accessing it through your navigationController.viewControllers. Index n-2 will be your previous view controller (n is the count of viewControllers).
I am trying to use popToViewController and it I keep getting the error "Tried to pop to a view controller that doesn't exist"?
I am in a Settings view and when the user clicks "Sign Out" I dismiss the Settings VC and segue back to the mainView where an unwind segue method is called. In the unwind segue method I call the following.
-(IBAction)endSettingsViaLogout:(UIStoryboardSegue *)segue {
//[self performSegueWithIdentifier:#"mainToLoginSegue" sender:self];
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:1] animated:YES];
//[self.navigationController popViewControllerAnimated:YES];
DLog(#"User finished with search");
}
When the poptoVC is called I get the "Tried to pop to a view controller that doesn't exist".
I NSLog the self.navigationController.viewControllers and I can see the stack of VC's and the one I want to pop to is in there?
/// UPDATE //////
Ok here is what I have found. If my segue to Settings is a regular "push" segue then the code works and I get popped back to where I want. If I do a custom segue, having come from the left side of the screen then it stops working. Even with the custom segue the self.navigationcontroller.viewcontrollers shows its in the stack. So why can't I pop back to it? Or how can I pop back to it with the custom segue?
Do I get the view controller hierarchy right?
On your basis UINavigationViewController you have set a main view controller as root view controller.
Then a settings view controller has been pushed upon this.
Via "Sign out" the settings view controller is being segued back to the main view controller.
If so, you are actually trying pop "back" to a view controller, that does not exist, since you already have reached the root view controller of your navigation controller stack. In this case, all previously initialized controllers have been popped from the stack and you would have to reinitialize and push the desired view controller.
If I am missing some important point, it would be helpful, if you would describe your actual view controller stack at the moment the "Sign out" option is available. Furthermore, what exactly is printed on the console if you log the self.navigationController.viewControllers array?
well that basically tells objectatindex 1 does not exist:
things to try:
objectatindex:0
or
nslog(#"%d",[[self.navigationController.viewControllers]count]);//add it before the popline and see if it works
if its the topview controller then try below:
[navigationController topViewController] instead
NSArray *viewControllers = [[self navigationController] viewControllers];
for( int i=0;i<[viewControllers count];i++)
{
id obj=[viewControllers objectAtIndex:[viewControllers count]-i-1];
if([obj isKindOfClass:[OrderCheckOutViewController class]]){
[[self navigationController] popToViewController:obj animated:YES];
return;
}
}
you can use the snippet to pop out to targetVC's next viewController in the navigationController's stack.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
BOOL findIt = NO;
UIViewController *targetVC = nil;
for (UIViewController *subVC in self.navigationController.viewControllers) {
if (findIt) {
break;
}
if (subVC == xxx) {
findIt = YES;
}else{
targetVC = subVC;
}
}
[self.navigationController popToViewController:targetVC animated:NO];
});
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];