Switching between sibling views via UINavigationViewController - ios

I am using a NavigationViewController to navigate between master view and detailed views. Now I want to be able to switch to a sibling detail view without first showing the master view and I have tried doing popViewController and pushViewController in the same method or popViewController in the detailed view and then pushViewController in mater's viewDidLoad after popViewController but it won't work - the view just ends up going back to the master view without switching to the detail. Any idea what to do?
The solution suggested here doesn't work as far as I can tell:
Switching Content Views Issue

I've never tried it, but this should work:
// create instance of new view controller
MyViewController *myViewController = [[MyViewController alloc] init];
// get current stack of viewControllers from navigation controller
NSMutableArray *viewControllers = [[self.navigationController viewControllers] mutableCopy];
// replace top view controller in stack
[viewControllers replaceObjectAtIndex:([viewControllers count] - 1) withObject:myViewController];
// set modified stack of view controllers in navigation controller
[self.navigationController setViewControllers:viewControllers animated:YES];
According to the docs, your app will transition to the new view controller with a push animation, and then when back button is clicked it it will be as if the view controller you pushed from was never there. (If you don't want the animation, use animated:NO)

Related

Modal Presentation Forcing Navigation Controller to Pop to Root

I am having a strange problem that I can't seem to find the cause for.
When attempting to present a modal view controller on a navigation controller the navigation controller is popping all of my view controllers underneath when the modal is dismissed.
So after pushing a few view controllers and presenting a modal on the topViewController, I end up back at the rootViewController when the modal is dismissed.
Anyone had this happen to them lately, I can't seem to find the reasoning for why this is happening?
This answer is for #rshev:
It was actually a user error. Here's what was happening: I had a view controller with a manually added navigationController on top of it (as a subview/child VC). The nav controller then had 3 VCs in its stack. The third (and visible) VC was presenting an image picker controller. When the image picker was dismissed, I momentarily saw my third VC , then it quickly popped back to the 1st, discarding the other two VC's from memory.
So what went wrong? What I didn't realize is that viewDidAppear (and viewWillAppear) was being called on my content view controller (the one with nav controller for its subview). This content VC was actually setting its navigation controller (and adding it as a subview) on viewDidAppear, thus covering up the original nav controller.
To solve it, I just added a static boolean to determine when the first VC FIRST appears, like so:
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
static BOOL firstAppearance = YES;
if (firstAppearance)
{
firstAppearance = NO;
UINavigationController *navController = [self.storyboard instantiateViewControllerWithIdentifier:#"NavigationController"];
[navController.view setFrame:self.view.bounds];
[self.view addSubview:navController.view];
[self addChildViewController:navController];
[navController didMoveToParentViewController:self];
}
}
Hope that helps.

How do I instantiate a storyboard with a given root viewcontroller and initial viewcontroller?

I have a storyboard in my application with a navigation controller and several views. This automatically puts a navigation bar with a back button into any views that are not the root view.
However, sometimes I navigate away from this storyboard to an individual nib. I want to navigate back to the storyboard, but not necessarily to the original root view. I currently use this method to do so:
+(void) TransitionOnStoryboard:(NSString*)storyboard to:(NSString*)identifier withViewController:(UIViewController*)viewController
{
UIStoryboard *sb = [UIStoryboard storyboardWithName:storyboard bundle:nil];
UIViewController *vc = [sb instantiateViewControllerWithIdentifier:identifier];
vc.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[viewController presentViewController:vc animated:YES completion:NULL];
}
This shows the view I want but without the navigation bar. How do I specify my navigation controller or root view, such that the app knows to put a navigation bar with a back button in?
Thanks
The answer is to leave your navigation controller underneath the view controller you add from a nib.
Present the nib as a full0-screen modal. That gets rid if your navigation bar, as desired. From that new view controller, you can push more modals, add a navigation controller, or whatever.
Note that you could do all of this and stay inside your storyboard as well.
Once you are done, dismiss the modal to reveal your navigation controller, and you are back in business with your storyboard. You can push a new view controller onto your navigation controller without animation and it should appear as the front-most VC when you pop the modal that came from a nib.
I'm sure that this isn't the ideal way to solve this problem, but it did work very nicely for me.
Essentially, I removed all the views from the view controller that had been generated since I navigated away from the storyboard, but before the current view and popped the current view. In this case, these views were of one class (CheckboxListViewController) and so could be removed quite simply as below:
+(void) navigateToMainMenu:(UINavigationController*)navigationController
{
[QuickView removeFromNavigationController:navigationController allOfViewControllerWithClass:[CheckboxListViewController class]];
[navigationController popViewControllerAnimated:YES];
}
+(void) removeFromNavigationController:(UINavigationController *)navigationController allOfViewControllerWithClass:(Class)viewControllerClass
{
NSMutableArray *keptViewControllers = [[NSMutableArray alloc]init];
for (UIViewController *viewController in navigationController.viewControllers)
if (![viewController isKindOfClass:viewControllerClass])
[keptViewControllers addObject:viewController];
navigationController.viewControllers = keptViewControllers;
}
(note- QuickView is the name of the class that contains these methods.).
Any other classes that you do not want your pop to navigate back to can be removed by calling:
[QuickView removeFromNavigationController:navigationController allOfViewControllerWithClass:[YourClassName class]];
In the navigateToMenu method.

How to switch between view controllers and get rid of the previous one

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];

Resetting the rootViewController on a NavigationController

I am using the PKRevealController that gives the side menu functionality to an application.
I Have UIbuttons in the side menu and they trigger a view to be pushed on the navigation stack. However, Each view being pushed has a back button that leads to the navigation view controller.
What would be the correct way to make sure that each view pushed onto the stack will become the root of the navigation stack? Except obviously not view controllers that I need to stack.
You can make it any view controller as the root view controller of the navigation controller for example
You can try this.....Just replace this code in FrontView controller didSelectRowAtindexPath
// [tableView deselectRowAtIndexPath:indexPath animated:YES];
self.navigationController.viewControllers = [NSArray arrayWithObject: [[LeftDemoViewController alloc]init]];
If you are not going to push view controllers, you may prefer to have a main view, which contains the view of the current view controller. Everytime you change to another app section you remove that view, and insert the view of the new viewcontroller.
But if you prefer to have a navigation stack you can remove the previous viewcontroller from the navigation stack and add the new one. Assuming you will only have one view controller at the same time and you are using a tab bar controller
UIViewController * root = nil;
UINavigationController *rootNavController = [[[UINavigationController alloc]initWithRootViewController:root] autorelease];
[root release];
NSMutableArray * viewControllersArray = [NSMutableArray arrayWithArray:self.tabBar.viewControllers];
[viewControllersArray removeObject:[viewControllersArray lastObject]];
[viewControllersArray addObject:rootNavController];
[self.tabBar setViewControllers:viewControllersArray animated:NO];
If you want to have more than one, instead of removing the last one all the time you will have to remove the first one. So you will have to replace lastObject with objectAtIndex:0 and addObject with insertObject:atIndex:0
Hope it helps

Setting up a UINavigation controller and various view loads

I have setup a UINavigation controller that uses the AppDelegate as the main point of contact.
I have different methods which run such as presentHomeViewController, presentLoginViewController, which push the different view controllers to the Navigation Controller.
App Delegate - didFinishLaunching
welcomeViewController = [[MyWelcomeViewController alloc] initWithNibName:#"MyWelcomeViewController" bundle:nil];
navController = [[UINavigationController alloc] initWithRootViewController:welcomeViewController];
navController.navigationBarHidden = YES;
self.revealSideViewController = [[PPRevealSideViewController alloc] initWithRootViewController:navController];
[self.revealSideViewController setDirectionsToShowBounce:PPRevealSideDirectionNone];
[self.revealSideViewController setPanInteractionsWhenClosed:PPRevealSideInteractionContentView | PPRevealSideInteractionNavigationBar];
self.window.rootViewController = self.revealSideViewController;
Is this the correct process for this?
- (void)presentHomeViewController {
// We start by dismissing the ModalViewConrtoller which is LoginViewController from the welcomeview
[self.welcomeViewController dismissModalViewControllerAnimated:YES];
// Check if the home view controller already exists if not create one
if (!self.homeViewController) {
NSLog(#"presentHomeViewController- Creating the Home View controller");
homeViewController = [[MyHomeViewController alloc] initWithNibName:#"MyHomeViewController" bundle:nil];
}
// Push the homeViewController onto the navController
NSLog(#"presentHomeViewController");
self.navController.navigationBarHidden = NO;
[self.navController setTitle:#"Home"];
[self.navController pushViewController:homeViewController animated:NO];
If I then add the following to a different class :
[self.navigationController pushViewController:accountViewController animated:NO];
No view is pushed to the stack, should I control all the movement within the AppDelegate as I have been doing, or is there betters way to approach this?
EDIT
Thanks for posting your code. So, to address your final question first, I don't recommend controlling your navigation stack from the app delegate. You should be controlling the stack from the view controllers that are the children of the navigation controller.
To that point, remember the hierarchy of view controllers: UINavigationController inherits from UIViewController, and UIViewController has properties defined for all the things you'd see in a navigation layout such navigation items and title. More importantly, it also has properties for its parent view controllers, the view controller that presented it, and its navigation controller. So, considering the hierarchy, your app delegate should only instantiate the navigation controller's root VC and the nav controller itself, and then subsequently set the nav controller's root VC.
From there, you should be pushing and popping other VCs from the VCs themselves. Remember, every VC has a property that's automatically set to point at the navigation controller it's a part of. That's why [self.navigationController pushViewController:] works. For instance, if I have a nav controller whose root VC is a UITableViewController, and tapping on one of the items in the table view pushed a new VC onto the stack, I would push that VC from the table VC and not from the nav controller class or the app delegate.
Sorry if that's confusing. Please let me know if that needs clarification and I'll give it my best. Otherwise, hopefully that gets you on the right track.

Resources