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

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

Related

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.

Jump to a specific view in navigation controller

I have 4 view controllers (A,B,C,D) and they are connected in this order, and a navigation controller pointed to A. My question is is there a way for me to jump from A to C directly?
You can create a UIStoryboardSegue that connects A to C, then call performSegueWithIdentifier: with the corresponding ID in the segue to trigger it.
try Unwind segue
http://chrisrisner.com/Unwinding-with-iOS-and-Storyboards
What are Unwind segues for and how do you use them?
For Swift 3
let viewControllers: [UIViewController] = self.navigationController!.viewControllers ;
for aViewController in viewControllers {
if(aViewController is YourViewController){
self.navigationController!.popToViewController(aViewController, animated: true);
}
}
Yes, all you need to do is instantiate the view controller, then pushViewController:animated:
UINavigationController should do the rest for you, including the popping when you're ready.
hope that helps, here is the source
If you are using nib then this code will help
NSArray *viewControllersArray=[self.navigationController viewControllers];
id viewController ;
UIViewController *homeVC=nil;
for(int i=0;i<[viewControllersArray count];i++)
{viewController = [viewControllersArray objectAtIndex:i];
if([viewController isKindOfClass:[classname class]])
{ homeVC=[viewControllersArray objectAtIndex:i];
break;
}
}if(homeVC)
{
[self.navigationController popToViewController:homeVC animated:YES];
}else{
classname *objViewController=[[classname alloc]init];
[self.navigationController pushViewController:objViewController animated:YES];
[objViewController release];
}
SWIFT
if you want to go back to a particular view controller
for viewcontroller in self.navigationController!.viewControllers as Array {
if viewcontroller.isKindOfClass(HomeVC) { // change HomeVC to your viewcontroller in which you want to back.
self.navigationController?.popToViewController(viewcontroller as! UIViewController, animated: true)
break
}
}
Go like this to a particular view controller
[self.navigationController popToViewController:[[self.navigationController viewControllers]objectAtIndex:1] animated:YES];
Yes You can. For safety, let's assume that you don't know, the order in which your View Controllers are connected. So, now, you just need a class assigned to your Controller, C. Let's Say, it is ViewController_C.h.
Step 1: Go to the class inspector in storyboard and assign this ViewController_C class to the controller, C.
Step 2: Now in the View Controller class which is assigned to your controller, A, write the following method.
-(int)getViewControllerIndex{
int i=0;
for(UIViewController *vc in [self.navigationController viewControllers]){
if([vc isKindOfClass:[ViewController_C class]]){
return i;
}
i++;
}
return 0;
}
and then call this method whenever you want to jump to Controller, C.
int index = [self getViewControllerIndex];
[self.navigationController popToViewController:[[self.navigationController viewControllers]objectAtIndex:index] animated:YES];

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 pop back to root view controller but then push to a different view?

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.

iOS – Showing a view controller already in the navigation stack

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
}

Resources