I'm hoping you can help me out. I've designed an app that is a Tab Bar app. In the view controller for the first tab, there is a button, that when pressed, generates a modal view. I've initialized a nav controller on that modal view, because when I hit the "Save" button on my modal view (which I use to input user data), I push another table view (which shows a table of all user inputted data so far). On that stacked table view, I have a "Done" button, which when pressed, should go to another view on the tab (a progress view of user input), that is, OFF the stack.
So my question is, if I'm two controllers into the stack, how to do I pop off the stack to another view NOT on the stack? I've used the popToViewController method, but as you may have guessed, I get the "Tried to pop to a view controller that doesn't exist" message. Here's my simple code in the second view on the stack:
- (IBAction)doneButtonPressed:(id)sender
{
LogTableViewController *logTableViewController = [[LogTableViewController alloc]init];
[self.navigationController popToViewController:logTableViewController animated:YES];
[logTableViewController release];
}
Where LogTableViewController is not on the stack, but is rather just another target for another tab in the app. Any ideas? Thanks in advance.
I'm not entirely sure why you just can't push the new view controller to the stack, but if you need to pop to it, you can do:
//create new VC
LogTableViewController *newVC = [[LogTableViewController alloc]init];;
//get VC stack
NSMutableArray * newControllers = [NSMutableArray arrayWithArray: self.navigationController.viewControllers];
//choose where to insert the new vc
NSUInteger insert_index = [newControllers count] - 1;
//insert into the stack
[newControllers insertObject:newVC atIndex:insert_index];
//replace stacks
[self.navigationController setViewControllers: newControllers];
//pop to your new controller
[self.navigationController popViewControllerAnimated:YES];
hope this helps.
You were close. Just do:
- (IBAction)doneButtonPressed:(id)sender
{
LogTableViewController *logTableViewController = [[LogTableViewController alloc]init];
[self.navigationController setViewControllers:[NSArray arrayWithObject:logTableViewController] animated:YES];
[logTableViewController release];
}
Related
I have a navigation view controller which navigates between some tableviews and I've just added an "edit" button inside the cells of one of the tables. What I'd like to happen is for the user to tap the edit button inside the cell and for the navigation controller to shunt across a new view where all of that cell's content is laid out for easy editing.
The cell, however, has no access to the navigation controller and cannot push a new view controller on to its stack. How can I do what I want?
Note that I am not using segues and storyboards as it's an old app and I want to continue supporting devices running iOS 4.
You should set the target of the button to the UIViewController that is already on the stack?
If you don't want to add this new view to the Navigation (Stack), Simply use the PresentModalViewController
// In action method for edit button
- (void)editButtonClicked {
EditViewController *editViewController = [[EditViewController alloc] initWithNibName:#"EditViewController" bundle:nil];
[self.navigationController presentModalViewController:editViewController animated:YES];
[editViewController release];
}
I'm not sure if you want this, but you can try:
// In action method for edit button
- (void)editButtonClicked {
EditViewController *editViewController = [[EditViewController alloc] initWithNibName:nil bundle:nil];
[self.navigationController pushViewController:editViewController animated:YES];
[editViewController release];
}
Im having a strange bug in my code while working on an app for iphone. I am creating a navigation controller and putting it within my tab bar controller and then when i move from my initial screen to a secondary screen the back button on the secondary screen points to itself. so when back is pressed it just reloads itself and then the next time it is pressed it goes back to the first screen.
UINavigationController *nav1 = [[UINavigationController alloc] init];
// create the initial views for each nav controller
SearchViewController *searchView = [[SearchViewController alloc] init];
searchView.title = #"Search";
//place all the viewcontrollers on the nav controllers ready to view
[nav1 pushViewController: searchView animated:NO];
//create tab bar out of above nav controllers
UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:#"Search" image:[UIImage imageNamed:#"search.png"] tag:0];
nav1.tabBarItem = item;
UITabBarController *tbc = [[UITabBarController alloc] init];
tbc.viewControllers = [NSArray arrayWithObjects:nav1, nil];
//
[nav1 release];
above code is in appdelegate for initial set up of nav + tab controller
SecondScreenViewController *sVC = [[SecondScreenViewController alloc] init];
sVC.title = #"Screen 2";
[self.navigationController pushViewController:sVC animated:YES];
[sVC release];
above is the code in the search view controller that loads the second screen / viewcontroller to the nav controller. But the back button at the top of screen 2 has the word screen 2 in it and when i press it once it reloads itself and then the back button says search (which is the title of first screen) . this time pressing it brings it back to the first screen.
Im just confused why the back button displays the view controllers own title name and reloads itself. I dont have any other code in these screens really, they are just blank screens with labels displaying which screen is there while i learn how to use it correctly
In the Apple Docs for UINavigationController for the designated initializer
- (id)initWithRootViewController:(UIViewController *)rootViewController
it is stated that:
Every navigation stack must have at least one view controller to act as the root.
I would recommend trying to initialize your UINavigationController with a rootViewController.
TiltedWindmill, I've experienced that problem before. In my case, in my head, I wanted to do this:
( > = pushing view controller )
Start at MapViewController > Details About An Attraction for a clicked pin on the map > MapViewController > Then pushing another DetailsViewController.
In my MapViewController, this view controller was registered for a notification. Theoretically, I thought I was navigating like this Map > Details View Controller > Map > Details View Controller. The thing was, whenever I tapped on the pin on the map, the Notification was fired and both copies of MapViewController that was on the navigation stack responded to the notification.
As a result it pushed two copies of the same DetailsViewController. So the real navigation was like this:
Map > Details View Controller > Map > Details View Controller copy1 + DetailsViewController copy2
*> = Second details view controller was pushed ontop of the previous one but not visible to the eye.
When I went to press the back button, it popped DetailsViewController copy2 and showed the same view controller (DetailsViewController copy1).
I fixed it by telling my MapViewController to unsubscribe from the notification on the event viewDidDisappear.
Not sure if you understood that :P
I want to push a view controller onto the stack, then pop the first one that pushed the new one.
-(void) someMethod {
MegaSuperAwesomeViewController *tempVC = [[MegaSuperAwesomeViewController alloc] init];
[self.navigationController pushViewController:tempVC animated:YES];
[tempVC release];
// pop this VC, how?
}
EDIT: turns out I can pop back 2 view controllers instead once finished with the new VC. Still not what I wanted exactly, but it works. The downside is I need to set a flag to indicate that the covered view is completed.
Here's a technique of popping back two view controllers, which has a similar problem of yours of the current view controller and its navigationController property going away as soon as you do the first pop:
// pop back 2 controllers on the stack to the setup screen
//
// locally store the navigation controller since
// self.navigationController will be nil once we are popped
//
UINavigationController *navController = self.navigationController;
// retain ourselves so that the controller will still exist once it's popped off
//
[[self retain] autorelease];
// Pop back 2 controllers to the setup screen
//
[navController popViewControllerAnimated:NO];
[navController popViewControllerAnimated:YES];
alternatively, you can directly "party" on the navigation controllers stack of view controllers:
setViewControllers:animated: Replaces
the view controllers currently managed
by the navigation controller with the
specified items.
(void)setViewControllers:(NSArray *)viewControllers animated:(BOOL)animated Parameters
viewControllers The view controllers
to place in the stack. The
front-to-back order of the controllers
in this array represents the new
bottom-to-top order of the controllers
in the navigation stack. Thus, the
last item added to the array becomes
the top item of the navigation stack.
animated If YES, animate the pushing
or popping of the top view controller.
If NO, replace the view controllers
without any animations. Discussion You
can use this method to update or
replace the current view controller
stack without pushing or popping each
controller explicitly. In addition,
this method lets you update the set of
controllers without animating the
changes, which might be appropriate at
launch time when you want to return
the navigation controller to a
previous state.
If animations are enabled, this method
decides which type of transition to
perform based on whether the last item
in the items array is already in the
navigation stack. If the view
controller is currently in the stack,
but is not the topmost item, this
method uses a pop transition; if it is
the topmost item, no transition is
performed. If the view controller is
not on the stack, this method uses a
push transition. Only one transition
is performed, but when that transition
finishes, the entire contents of the
stack are replaced with the new view
controllers. For example, if
controllers A, B, and C are on the
stack and you set controllers D, A,
and B, this method uses a pop
transition and the resulting stack
contains the controllers D, A, and B.
Availability Available in iOS 3.0 and
later. Declared In
UINavigationController.h
So, to "disappear" the view controller directly under you on the navigation stack, in your view controller's viewDidLoad, you could do this:
NSMutableArray *VCs = [self.navigationController.viewControllers mutableCopy];
[VCs removeObjectAtIndex:[VCs count] - 2];
self.navigationController.viewControllers = VCs;
I had trouble figuring this out also so I wanted to share how I got this to work.
Let's say you have a stack of VCs VC1 being the root then you push VC2 and from VC2 you want to push VC3 but once pushed you don't want the user to go back to VC2 but rather to VC1 (the root). The way to do that is:
//push VC3 from VC2
[[self navigationController] pushViewController:VC3 animated:YES];
// now remove VC2 from the view controllers array so we will jump straight back to VC1
NSMutableArray *viewHeirarchy =[[NSMutableArray alloc] initWithArray:[self.navigationController viewControllers]];
[viewHeirarchy removeObject:self];
self.navigationController.viewControllers = viewHeirarchy;
Hope this helps someone else
Thanks Bogatyr about the tip on 'party on the viewcontroller array for the navcontroller'. I just replaced the entire stack with the one viewcontroller I want to change to, and then log out all the viewcontrollers in the stack to make sure its the only one! Worked great - thanks!
RatingsTableViewController *newViewController = [[RatingsTableViewController alloc] init];
NSMutableArray * newVCarray = [NSMutableArray arrayWithObjects:newViewController, nil];
self.navigationController.viewControllers = newVCarray;
[newViewController release];
NSMutableArray *allControllers = [[NSMutableArray alloc] initWithArray:self.navigationController.viewControllers];
for (id object in allControllers) {
NSLog(#"name VC: %#", object);
}
[allControllers release];
-(void)popToSelf{
NSArray *array = [self.navigationController viewControllers];
for (int i = 0 ; i < array.count ; i++) {
UIViewController *currentVC = [array objectAtIndex:i];
if ([currentVC isKindOfClass:[YourViewControllerClass class]]) {
[self.navigationController popToViewController:[array objectAtIndex:i] animated:YES];
}
}
}
I have a navigation based app. The root view is a list of items. From this root view you can tap on a table cell to an item's detail view. Or you can go to a form view to create a new item via the 'Add' button in the nav bar.
My question is how I can jump from the form view to the detail view once the new object has been created?
I don't want to push the detail view on top of the form view, because I want the root table view to be what the user see's after pushing the 'back' nav button form the detail view.
I've tried the following. It pops to the root view fine, but doesn't push the detail view after that..
[context save:&error];
[self.navigationController popToRootViewControllerAnimated:NO];
// display detail view
GoalDetailViewController *detailViewController = [[GoalDetailViewController alloc] initWithNibName:#"GoalDetailViewController" bundle:nil];
// Pass the selected object to the new view controller.
detailViewController.goal = goal;
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
Any help and direction would be much appreciated :)
Cheers!
Generally you would implement the add button using a view controller displayed modally.
[self presentModalViewController:modalViewController animated:YES];
meaning it appears from the bottom of the screen (see adding a contact). Then when they press done in the top right you can push the detail view controller on the navigation controller without animating it, making the back button go back to the original list view.
This isn't something you see too often in apps, but it can be accomplished like this:
// Get the current view controller stack.
NSMutableArray *viewControllers = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
// Instantiate your new detail view controller
GoalDetailViewController *detailViewController = [[GoalDetailViewController alloc] initWithNibName:#"GoalDetailViewController" bundle:nil];
detailViewController.goal = goal;
// Remove the topmost view controller from the stack
[viewControllers removeLastObject];
// Replace it with the new detail view controller
[viewControllers addObject:detailViewController];
// Change the view controller stack
[self.navigationController setViewControllers:viewControllers animated:YES];
// Clean up
[detailViewController release];
Exactly what animation you get is described here.
I'm changing the back button item title in the viewDidAppear of a controller in the following way:
self.navigationController.navigationBar.backItem.title = #"Previous";
It changes the tittle properly, but the I'm having a strange behaviour. When I select the "previous" button, it changes the tittle of the controller that is up in the stack (i.e the parent controller now has the title "Previous".
Do you now why this happened?
When you're using a navigation controller, calling [self setTitle:#"Title"]; inside of any view controller in the stack will set the navigation bar title. This is also the title used by default for the back button when you've pushed a new view controller. Apparently, from what you are experiencing, explicitly setting the title of the backItem, also sets it for the navigation bar title for the previous view controller overriding whatever what specified in the call to -setTitle in the view controller.
You will probably be better off just managing the title from within the view controllers in your navigation stack. When you go to push a new view controller, do this:
[self setTitle:#"Previous"];
NextViewController *controller = [[NextViewController alloc] init];
[[self navigationController] pushViewController:controller animated:YES];
[controller release], controller = nil;
Now, when the next view controller displays, the back button with say "Previous". Now, you just need to change it back to whatever its real title should be in -viewWillAppear:
- (void)viewWillAppear:(BOOL)animated;
{
[self setTitle:#"Real Title"];
[super viewWillAppear:animated];
}
It may feel a little hacky, but it's better than trying to override the navigation bar functionality. Wrestling with the nav bar/nav controller stack can prove very frustrating.
Best regards.