Switching to an arbitrary view using `UINavigationController` - ios

I have an iPhone app that uses a UINavigationController that is created as so:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Create navigation controller and initialize it with the menu view controller.
navigationController = [[UINavigationController alloc] initWithRootViewController:[[MenuViewController alloc] init]];
navigationController.navigationBar.hidden = YES;
navigationController.toolbar.hidden = YES;
// Create main window and initialize it with navigation view controller.
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[window setRootViewController:navigationController];
[window makeKeyAndVisible];
return YES;
}
From there things usually happen in a sequence similar to the following:
Push SelectDifficultyViewController
Push GameViewController
Push GameOverViewController
Pop to root (MenuViewController)
Instead of popping to the root in step 4, how would I go about switching to a new instance of GameViewController.
I currently have the following but it just returns me to the root:
[self.navigationController popToRootViewControllerAnimated:NO];
[self.navigationController pushViewController:[[GameViewController alloc] initWithStuff:stuff] animated:NO];

As it turns out, the line [self.navigationController popToRootViewControllerAnimated:NO] will result in self.navigationController being nil, which is why the subsequent push to the navigationController does nothing. To fix this, store a local copy of the navigation controller and use that to push after popping to root. Credit for this answer comes from here:
UINavigationController popToRootViewController, and then immediately push a new view

mehinger's answer solved my problem, but I wanted to make it easier to use, so I made the following category.
UINavigationController+PopAndPush.h
#interface UINavigationController(PopAndPush)
- (void)popAndPushToViewController:(UIViewController *)controller animated:(BOOL)animated;
- (void)popAndPushToViewController:(UIViewController *)controller withCustomTransition:(CustomViewAnimationTransition)transition;
#end
UINavigationController+PopAndPush.m
#import "UINavigationController+PopAndPush.h"
#implementation UINavigationController(PopAndPush)
- (void)popAndPushToViewController:(UIViewController *)controller animated:(BOOL)animated {
[self popToRootViewControllerAnimated:NO];
[self pushViewController:controller animated:animated];
}
- (void)popAndPushToViewController:(UIViewController *)controller withCustomTransition:(CustomViewAnimationTransition)transition {
[self popToRootViewControllerAnimated:NO];
[self pushViewController:controller withCustomTransition:transition subtype:nil];
}
#end

Related

How to return to root ViewController, if the current is deeply nested?

I have a problem that my iOS app has several ViewControllers.
For example: ViewControllers named like A, B, C. A jumped to B with pushViewController, B jumped to C with presentViewController , C jumped to D with presentViewController and so on.
If the current ViewController is Z or some other ViewController, how can I jump back to A directly?
Try Below code. It will work for all cases :
for (UIViewController *controller in self.navigationController.viewControllers)
{
if ([controller isKindOfClass:[nameOfYourViewControllerYouWantToNavigate class]])
{
if(controller.isBeingPresented)
[controller.presentingViewController dismissViewControllerAnimated:YES completion:NULL];
else
[self.navigationController popToViewController:controller animated:YES];
break;
}
}
[[self navigationController] popToRootViewControllerAnimated:YES];
It will pop your viewcontroller to Root controller.
For the view controllers show by pushViewController, you can get rootViewController via [self.navigationController.viewControllers objectAtIndex:0]
Others shown by presentViewController, you can get parent view controller via self.presentingViewController
Whatever you want to go back to A ViewController from any other view controller, first you have to set that as RootViewController. If you use XIB you must set as root in app delegate didFinishLaunchWithOptions method. If you use storyboard you should set NavigationController in storyboard and set AViewController as ROOT using control+drag(mouse).
I work in a project which has lot of view controllers. It has pushViewController and PresentViewControllers also I set A asRootViewController. So I can return from any view controller to RootViewController.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
self.window = [[UIWindow alloc]initWithFrame:[[UIScreen mainScreen] bounds]];
AViewController *aVC = [[AViewController alloc]initWithNibName:#“AViewController" bundle:nil];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:aVC];
self.window.rootViewController = aVC;
[navController setNavigationBarHidden:YES];
self.window.backgroundColor = [UIColor clearColor];
[self.window makeKeyAndVisible];
return YES;
}
For going to AViewController from any other view controller
- (IBAction)actionGoBack:(id)sender
{
[self.navigationController popToRootViewControllerAnimated:YES];
}

open rootViewController with button

I recently work on a tutorial for my app. I created the tutorial with this Tutorial:
http://www.appcoda.com/uipageviewcontroller-tutorial-intro/
I created a "button" which brings the user back to the rootViewController, in this case the TabBarController. Problem is: with this tutorial I made a extra storyboard for the tutorial. So how can I go back to the original rootViewController(TabBarController) with the button?
Code:
- (IBAction)start:(id)sender {
UIViewController* backToRootViewController = [[UIViewController alloc] initWithNibName:#"TabBarController" bundle:[NSBundle mainBundle]];
[self.view addSubview:backToRootViewController.view];
}
This does not work, too
- (IBAction)start:(id)sender {
[self.navigationController popToRootViewControllerAnimated:YES];
}
EDIT
To open the tutorial at the first start:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
BOOL isAccepted = [standardUserDefaults boolForKey:#"iHaveAcceptedTheTerms"];
if (!isAccepted) {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.viewController = [[APPViewController alloc] initWithNibName:#"APPViewController" bundle:nil];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
}
APPViewController is the Tutorial
EDIT
After the help of johnnelm9r the current code is this:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"main" bundle: nil];
IntroViewController *introViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"IntroViewController"];
BOOL isAccepted = [standardUserDefaults boolForKey:#"iHaveAcceptedTheTerms"];
if (!isAccepted) {
[self.window.rootViewController presentViewController:introViewController animated:NO completion:nil];
}
But now, sadly, the app crashed and the error is:
Application tried to present a nil modal view controller on target <UITabBarController: 0x175409d0>.'
and also a warning: Incompatible pointer type assigning to 'ViewController' from 'UIViewController' in AppDelegate
*****UPDATE****
I uploaded a sample project to demonstrate what I mean. You can find it here: github link
Good luck!
**** EDIT
After talking with you more I'd suggest trying to use something similar to this code in your viewDidAppear method of whatever view controller runs the first tab of your tabbarcontroller (obviously you want your own class name where I have IntroViewController):
BOOL didShowIntro = [[NSUserDefaults standardUserDefaults] boolForKey:#"showIntro"];
IntroViewController *introViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"IntroViewController"];
if (didShowIntro)
{
[self presentViewController:introViewController animated:NO completion:nil];
}
and then just pop the presented controller from your button in the tutorial like so:
[self dismissViewControllerAnimated:NO completion:nil];
Just remember to set your user defaults to no when you press the button in your tutorial view controller. And make sure you are going from tabbarcontroller to navigationcontroller as a relationship segue and then to the view controller you want to show after the tutorial as a root. Just to be clear: there should be no connection to the tutorial view controller on your storyboard.
Just to be extra clear: your storyboard should have the navbarcontroller as your initial view controller connected to a navigationcontroller connected to a view controller and another view controller not connected to anything. :) And make sure you delete the code in the delegate.
To go to rootViewController
[self.navigationController popToRootViewControllerAnimated:YES];

For iOS, Any way to know the navigation source?

Any way to know the navigation source?
For example, the navigation stack has A/B/C three view controllers.
If C is popped, when B is displayed, any way to know the navigation is from C to B ?
Thanks a lot in advance.
in another simple method
first declare the UINavigationController in Appdelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.viewController = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
UINavigationController *nav=[[UINavigationController alloc]initWithRootViewController:self.viewController];
self.window.rootViewController = nav;
[nav setNavigationBarHidden:YES];
[self.window makeKeyAndVisible];
return YES;
}
after that in your first view controller.m import the second view controller header file
#import "B.h"
in your button action
- (IBAction)butvie:(id)sender {
B*tab=[[Balloc]init];
[self.navigationController pushViewController:tab
animated:YES];
}
in C viewcontroller comes to back of B
- (IBAction)butvie:(id)sender {
[self.navigationController popViewController
animated:YES];
}
You can track this manually. You can keep a global variable in your AppDelegate class and set that variable whenever you pop a particular viewController.
UPDATE after comments:
In this case, you can use NSUserDefaults or you can post an NSNotification object from the poppedViewController. Though I am not sure, how much efficient these options are for you to use.
You can tell whether a controller appeared because it was added to the stack, or because another controller was popped off the stack using isMovingToParentViewController. If you have this code in B, it will tell you which happened:
-(void)viewDidAppear:(BOOL)animated {
if ([self isMovingToParentViewController]) {
NSLog(#"Coming from A");
}else{
NSLog(#"Coming from C");
}
}

No navigation bar in view

I have following structure in my iOS app:
RootViewController (table view controller showing core data).
On RootViewController is a view including 4 buttons.
Button 1: Menu...
After pressing button 1, NSManagedObjectContext is passed to MenuViewController.
On MenuViewController there are several buttons, one of them is used to open DoneViewController, which is a duplicate from RootViewController (only NSPredicates changed to show different core data objects.
DoneViewController is showing correctly the expected rows, but it was supposed to have a navigation bar, which is not shown.
This is the code used to open DoneViewController:
- (IBAction)doneToDoaction:(id)sender {
DoneViewController *viewController = [[DoneViewController alloc] init];
viewController.managedObjectContext = [self mainContext];
[self presentViewController:viewController animated:YES completion:nil];
}
What should I do to open the view and have a navigation bar like RootViewController does?
I will now put all the navigation controller instances that are now in my app:
//AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Fetch the data to see if we ought to pre-populate
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
[self loadFavoriteThingsData];
RootViewController *rootViewController = (RootViewController *)
[navigationController topViewController];
[rootViewController setManagedObjectContext:[self managedObjectContext]];
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
return YES;
}
//RootViewController
- (IBAction)MenuToDoAction:(id)sender {
MenuViewController *viewController = [[MenuViewController alloc] init];
viewController.mainContext = [self managedObjectContext];
[self presentViewController:viewController animated:YES completion:nil];
}
//MenuViewController
- (IBAction)doneToDoaction:(id)sender {
DoneViewController *viewController = [[DoneViewController alloc] init];
viewController.managedObjectContext = [self mainContext];
[self.navigationController pushViewController:viewController animated:YES];
}
The last doneToDoaction method, as proposed by #EricLee, doesn't throw an exception, but the button action is not executed and the app freezes...
Instead of presenting DoneViewController, you want to push it onto the current navigation controller.
It is the navigation controller (self.navigationController) that has the view containing the navigation bar. Pushing a view controller onto it will cause that view controller's view to appear, along with the navigation bar, inside the navigation controller's view.
Add navigation controller when present view controller
- (IBAction)doneToDoaction:(id)sender {
DoneViewController *viewController = [[DoneViewController alloc] init];
viewController.managedObjectContext = [self mainContext];
UINavigationController * nVC=[[UINavigationController alloc] initWithRootViewController:viewController];
[self presentViewController:nVC animated:YES completion:nil];
}
If you use storyboard or xib and do not want to push DoneViewController to UINavigationViewController, I suggest you to make a gap for status bar manually in storyboard or xib.
In iOS7, the status bar is transparent. therefore, the top of your VC is extended under it.
If you push it to UINavigationViewController and navigation bar is opaque, there is nothing to do. But, if you don't do that, set a gap manually.
EDIT
Sorry, I found another way better than what I suggest you before. You don't need transparent status bar, set status bar style option to Black Opaque in General tab in project configuration.
You can just push the To-be-presented viewController into the current( presenting) viewcontroller's navigationController.
- (IBAction)doneToDoaction:(id)sender {
DoneViewController *viewController = [[DoneViewController alloc] init];
viewController.managedObjectContext = [self mainContext];
[self.navigationController pushViewController:viewController animated:YES];
}
In your AppDelegate didFinishLaunchingWithOptions
self.window = [[UIWindo walloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.controller = [[ViewController alloc] init];
UINavigationController *nav = [[UINavigationController alloc]initWithRootViewController:self.controller];
self.window.rootViewController = nav;
[self.window addSubview:nav.view];
[self.window makeKeyAndVisible];

Back button disappears after pushing two views using UINavigationController

I'm currently developing an app using a UINavigationController. I set the root view controller to ViewController1 and then push ViewController2 and then ViewController3 in response to button click events.
If I then click the back button from view 3, I'm returned to view 2 but this view has no back button. Interestingly as well, having set titles for each of these views ('View 1', 'View 2' and 'View 3' respectively), if I navigate from view 3 back to view 2 using the back button, the title changes to 'View 1' i.e. the title for the initial view (view 1) - not the title for view 2.
If anyone has any idea what might be going on here, your suggestions are very much appreciated.
Many thanks in advance!
Edit: I use the following code to init the UINavigationController in the app delegate:
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen] bounds]];
self.viewController1 = [[ViewController1 alloc] init];
self.viewController2 = [[ViewController2 alloc] init];
self.viewController3 = [[ViewController3 alloc] init];
self.navigationController = [[UINavigationController alloc] initWithRootViewController:viewController1];
self.window.rootViewController = self.navigationController;
I later push view controllers to the UINavigationController on button clicks as follows:
MyAppDelegate *appDelegate = (MyAppDelegate*)[[UIApplication sharedApplication] delegate];
[self.navigationController pushViewController:appDelegate.viewController2 animated:YES];
I found the solution - in viewController2 and viewController3 I had the following code in order to hide the navigation bar (I wanted the navigation bar hidden on view1 and then visible on views 2 and 3).
- (void) viewWillAppear:(BOOL)animated
{
[self.navigationController setNavigationBarHidden:NO animated:animated];
[super viewWillAppear:animated];
}
- (void) viewWillDisappear:(BOOL)animated
{
[self.navigationController setNavigationBarHidden:YES animated:animated];
[super viewWillDisappear:animated];
}
I realised it makes far more sense to do the reverse in viewController1 i.e.
- (void) viewWillAppear:(BOOL)animated
{
[self.navigationController setNavigationBarHidden:YES animated:animated];
[super viewWillAppear:animated];
}
- (void) viewWillDisappear:(BOOL)animated
{
[self.navigationController setNavigationBarHidden:NO animated:animated];
[super viewWillDisappear:animated];
}
and then removing the previous code from view controllers 2 and 3. This solved the issue.

Resources