I have a tabBarController with two tabs, first of which contains an instance of NavigatorController. The navigatorController is initiated with a custom viewController "peersViewController" that list all the network peers on a tableView. Upon selecting a peer, an instance of "FilesListViewController" (which list files in the c:\ directory) is pushed into the navigationController stack.
In this filesListViewController I have a button to let it navigate to say documents directory. To do this I'd wired the interface to call a gotoDirectory:(NSString*)path method in the rootViewController:
- (void)gotoDirectory:(NSString*)path {
[[self navigationController] popToRootViewControllerAnimated:YES];
NSArray *files = [self getFilesFromPeerAtPath:path];
FilesListViewController *filesVC = [[FilesListViewController alloc] initWithFiles:files];
[[self navigationController] pushViewController:filesVC animated:YES];
[filesVC release];
}
However, when I press that button, the navigationController did pop my view to the root view controller, but then the FilesListViewController that I instantiated did not appear. From the log, I know that the custom initWithFiles method was indeed called and network stuffs did happen to get the file names.
Something else is screwy about this. I tried clicking on the second tab and then click back to the first tab, and huala! the file names I needed are there. It looks like the data and the filesListViewController was indeed pushed into the navigatorController stack, but the display was not refreshed but stuck at the screen of rootViewController (peersViewController).
Am I doing anything wrong?
--Ben.
-- Edited like 15 minutes after posting the question. I'd found a workaround, but it bothers me that pop and then push doesn't work.
- (void)gotoDirectory:(NSString*)path {
PeersListViewController *rootViewController = (PeersListViewController*)[[[self navigationController] viewControllers] objectAtIndex:0];
[[self navigationController] setViewControllers:[NSArray arrayWithObject:rootViewController]];
FilesListViewController *filesVC = [[FilesListViewController alloc] initWithFiles:files];
[[self navigationController] pushViewController:filesVC animated:YES];
[filesVC release];
}
It doesn't seem like the navigationController should be circumvented this way, and I'd probably have to release all the viewControllers that were in the original stack. This does however work on the iphone 3.0 simulator.
If I'm using this code though, how should the memory release be handled? should I get the original NSArray of viewcontrollers and release everything?
The problem and solution to this issue is actually extremely simple.
Calling [self.navigationController popToRootViewControllerAnimated:YES] sets self.navigationController to nil. When you subsequently call [self.navigationController pushViewController:someOtherViewController] you are effectively sending a message to nil, which does nothing.
To workaround, simply set up a local reference to the navigationController and use that instead:
UINavigationController * navigationController = self.navigationController;
[navigationController popToRootViewControllerAnimated:NO];
[navigationController pushViewController:someOtherViewController animated:YES];
As stated by Jason, the popToRootViewController must be performed without animation for this to work correctly.
Thanks go to jpimbert on the Apple forums for pointing this out.
I got a very similar problem (but without using tab).
I got three viewController : main(root), form and result.
when the UINavigationController stack is
"main -> result"
on a btnClick I do a popToRootViewControllerAnimated then a push of the formViewCtrl.
in order to have
"main -> form"
the navbar title and back button label are correct and the formViewCtrl's event are called.
BUT, I still see the main view.
Here is my "solution"
After doing some test, I found out that without the animation to go to the rootViwCtrl this work fine. So I only use the animation to push viewCtrl.
iPhone 3.0, problem found on device & simulator.
If i got something new, i will update/comment my post.
I see that this question about popping to the root and then pushing a new ViewController is pretty prevalent, and this post is viewed a lot, so I wanted to add my bit to help other new guys out, especially those using Xcode 4 and a storyboard.
In Xcode 4, you have a storyboard. Let's say you have these view controllers: HomeViewController, FirstPageViewController, SecondPageViewController. Make sure to click each of them and name their identifiers by going to the Utilities pane->Attributes Inspector. We'll say they're named Home, First, and Second.
You are Home, then you go to First, then you want to be able to go to Second and be able to press the back button to go back to Home. To do this, you want to change your code in FirstPageViewController.
To expand on the example, make a button in FirstPageViewController in the storyboard. Ctrl-drag that button into FirstPageViewController.m. In there, the following code will achieve the desired outcome:
// Remember to add #import "SecondPageViewController.h" at the top
SecondPageViewController *secondView = [self.storyboard instantiateViewContorllerWithIdentifier:#"Second"];
UINavigationController *navigationController = self.navigationController;
NSArray *array = [navigationController viewControllers];
// [array objectAtIndex:0] is the root view controller
NSArray *viewControllersStack = [NSArray arrayWithObjects:[array objectAtIndex:0], secondView, nil];
[navigationController setViewControllers:viewControllersStack animated:YES];
Basically, you're grabbing the view controllers, arranging them in a stack in the order you want, and then having the navigation controller use that stack for navigation. It's an alternative to pushing and popping.
I found a workaround but I cannot explain why it is working:
1. First push the needed controller.
2. Then pop to the one you want to.
This is totally illogical, but it works for my case.
Just to make things clear, I'm using it in the following scenario:
First Screen -> Goes to Loading Screen -> Second Screen
When I'm on the Second Screen, I don't want to have the Loading Screen in the stack and when click back I should go to the First Screen.
Regards,
Vesko Kolev
You can actually keep the "Go back" animation, followed by the "Go forward" animation by basically delaying the push animation till after the pop animation is complete. Here is an example:
(Note: I have an NSString variable called "transitionTo" in my appDelegate that's initially set to #"")...First, set that variable to an NSString you can detect for later. Then, pop the controller to give you a nice screen transition back to the root:
appDelegate.transitionTo = #"Another";
[detailNavigationController popToRootViewControllerAnimated:YES];
Then inside the rootviewcontroller's class, use the viewDidAppear method:
-(void)viewDidAppear:(BOOL)animated
{
AppDelegate *appDelegate =(AppDelegate*) [UIApplication sharedApplication].delegate;
if([appDelegate.transitionTo isEqualToString:#"Another"])
{
[self transitionToAnotherView];
appDelegate.transitionTo = #"";
}
}
-(void)transitionToAnotherView
{
// Create and push new view controller here
AnotherViewController *controller = [[AnotherViewController alloc] init];
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:#"Home" style:UIBarButtonItemStyleBordered target:nil action:nil];
[self.navigationItem setBackBarButtonItem:backButton];
[[self navigationController] pushViewController:controller animated:YES];
}
So basically, pop to the root...when the transition finishes at "viewDidAppear"...then push the next view. I happened to keep a variable to tell you which view you wish to transition to (with #"" meaning not to do a transition in the case that I want to stay on this screen).
Nick Street's answer works great if you want to popToRootViewController and subsequently push another VC.
VC1 -> VC2 -> VC3: hit the back button from VC3 => VC2, then VC1, here OK
However, when VC1 pushes VC2, which in turn pushes VC3, then going back to VC1 directly from VC3 does not work as wished:
I've implemented in VC3's -(void)viewWillDisappear:(BOOL)animated:
-(void)viewWillDisappear:(BOOL)animated{
...
[self.navigationController popToRootViewControllerAnimated:YES];
}
I also tried to implement it in the "back button", same result: upon hitting the back button from VC3 to go back to VC1: it breaks. The actual VC is VC1, but the navigation bar is still VC2. Playing with other combinations, I get VC1's navBar on VC2. Total mess.
Loda mentioned something about timing. I think that's the main issue here. I've tried a few things, so maybe I'm missing out something here, but this is what worked for me, at last:
In VC3:
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
// notify VC2
[[NSNotificationCenter defaultCenter] postNotificationName:backFromV3 object:self];
}
In VC2:
-(void)viewDidLoad {
...
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(backFromV3)
name:#"BackFromV3"
object:nil];
}
-(void)backFromV3{
[NSTimer scheduledTimerWithTimeInterval:0.5
target:self
selector:#selector(backToRootViewController)
userInfo:nil
repeats:NO];
}
-(void)backToVC1 {
self.navigationItem.rightBarButtonItem = nil;
[self.navigationController popToRootViewControllerAnimated:YES];
}
Of course, do the necessary cleaning.
The timer is critical here. If 0, it breaks. 0.5 seems to be alright.
That works perfectly for me. A little heavy, but I have not been able to find anything that does the trick.
Related
How can i go back 2 or 3 views back without using navigation controller? That is in my app, there is a main menu view. i want to reach that menu from all the other pages (from multiple views). How can this be implemented? with
[self dismissModalViewControllerAnimated:YES]; this cannot be implemented i suppose.
Anybody please help me..
I found the solution.
Of course you can find the solution in the most obvious place so reading from the UIViewController reference for the dismissModalViewControllerAnimated method ...
If you present several modal view controllers in succession, and thus build a stack of modal 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.
so it's enough to call the dismissModalViewControllerAnimated on the target View. I used the following code:
[[[[[self parentViewController] parentViewController] parentViewController] parentViewController] dismissModalViewControllerAnimated:YES];
to go back to my home.
Copied from here
"Going 2 views back without navigation controller"
Hmmm, I'm not sure if this misses the point, but the easiest way to do this is to use the popToRootViewControllerAnimated in the Navigation Controller:
[self.navigationController popToRootViewControllerAnimated:TRUE];
So, supposing you had a series of three screens in a Navigation Controller, and on the third screen you wanted the "Back" button to take you back to the initial screen.
On the third screen, you would add this code:
-(void)viewDidLoad
{
[super viewDidLoad];
// change the back button and add an event handler
self.navigationItem.leftBarButtonItem =
[[UIBarButtonItem alloc] initWithTitle:#"Back"
style:UIBarButtonItemStyleBordered
target:self
action:#selector(handleBack:)];
}
-(void)handleBack:(id)sender
{
NSLog(#"About to go back to the first screen..");
[self.navigationController popToRootViewControllerAnimated:TRUE];
}
Do you want to go "two or three VIEWS back"? Use removeFromSuperview? Or are you talking about ViewControllers?
Are you using Storyboard?
If yes do:
- (void)showModalAssistantViewController
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"AssistantStoryboard" bundle:nil];
AssistantRootViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:#"AssistantNavigationController"];
[viewController setModalPresentationStyle:UIModalPresentationFullScreen];
[viewController setModalTransitionStyle:UIModalTransitionStyleCoverVertical];
[self.navigationController presentModalViewController:viewController animated:YES];
//... or pushToViewController ... whatever, you get the point.
}
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:1] animated:YES];
I am using this code to hide a button in a different view controller, but the button does not get hidden when the button is pressed to hide the button in the other view controller.
This is the code I am using to hide the button in the other view controller:
[self dismissModalViewControllerAnimated:YES];
NSLog(#"Exited");
ViewController *vc = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
[vc.mainbutton1 setHidden:YES];
Why is this not working?
Thanks!
take a BOOL variable in ViewController controller and make the property and synthesize also.
and do this.
ViewController *vc = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
vc.check = YES;
in the view controller viewdidload
write this
if(self.check)
[mainbutton1 set hidden:YES];
The other answers should work unless...
Judging by your code I am going to guess that you are trying to hide a button on the viewController that presented the modal view?
If this is correct then what you are doing will not work as you are creating a new instance of ViewController which is not the already existing viewController you want to use.
Although the docs say that it is fine to call [self dismissModalViewControllerAnimated:YES]; from the presented modal view I tend to set up a delegate to handle the dismissal like in Apple's utitliy app template.
The reason this isn't working is because even though you have alloc'd and init'd the ViewController properly, the actual elements of that vc ViewController (including mainbutton1) have not been loaded yet.
Hitman has the right idea (and I'm voting his idea up).
Either put in a BOOL property for setting mainButton1 to hidden when the view appears, or call your [mainButton1 setHidden: YES] right after you explicitly display the view (via animation or adding subviews or whatever).
From your question it sounds like you want to hide the button in an existing view controller, whereas in your code you are creating a new one
ViewController *vc = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
[vc.mainbutton1 setHidden:YES];
Either the view controller which you observe is not the one you expect or the mainbutton1 outlet is not connected properly. You can check if the memory controller is the one you expect by logging its memory address.
NSLog(#"Hid button for view controller %p", vc);
And doing the same in the viewDidAppear callback of ViewController
NSLog(#"In viewDidAppear for view controller %p", self);
It seems you want a certain button to be hidden if something has been happening somewhere else.
You COULD, somewhat as a hack (but I don't mind that very much) control this with a variable on your AppDelegate for instance.
When the "something" is happening "somewhere else", do this:
MyAppDelegate *appDelegate = [[(MyAppDelegate *)UIApplication sharedApplication] delegate];
appDelegate.shouldHideThatOtherButtonLater = YES;
Then, when you create your new ViewController later on you could use this value to determine if your button should be visible or not like this:
ViewController *vc = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
MyAppDelegate *appDelegate = [[(MyAppDelegate *)UIApplication sharedApplication] delegate];
[vc.mainbutton1 setHidden: appDelegate.shouldHideThatOtherButtonLater ];
You will in this case have to prepare your AppDelegate for this by creating and synthesizing that shouldHideThatOtherButtonLater-property.
I have a simple app that have 3 views, HomeView, MenuView and GameView.
In the HomeView I have 2 buttons (Menu and Start Game). When the menu button is clicked, I open the MenuView using the following code:
- (IBAction)displayMenu:(id)sender{
MenuView *mv = [[MenuView alloc] init];
[self.view addSubView:[mv view];
[mv release];
}
In the MenuView, I have a button that will allow the user to return to the HomeView. When this button is clicked, I use the following code to return to the HomeView
- (IBAction)returnToHome:(id)sender{
HomeView* hv = [[HomeView alloc] init];
[self.view addSubView:[hv view];
[hv release];
}
The above code is working but is this the correct way of doing it? I was under the impression that when I call the addSubView, the view will be retain so If keep going back and forth between HomeView and MenuView, will i have multiple instance of HomeView and MenuView retained since I keep calling addSubView from each of the view?
Thank you.
You could use the UINavigationController, which will allow you to push UIViewControllers on to the stack.
Using the UINavigationController you will get an nice naviagtionbar in at the top of you screen and the back button.
You can find a nice example here:http://developer.apple.com/library/ios/#documentation/uikit/reference/UINavigationController_Class/Reference/Reference.html
I found this way the most useful and convenient. When calling the new view use this:
HomeView* hv = [[HomeView alloc] init];
(here you can add a uninavigation controller)
[self presentModalViewController:hv animated:YES];
Then to dismiss this view and go back use this:
[self dismissModalViewControllerAnimated:YES];
#atbebtg:
There is a way to do that, infact there are several, since there not really is a "right way" to do it.
For me this works well:
[[self navigationController] setNavigationBarHidden:YES animated:NO];
This will hide the Navigation Bar, so the user can't go back to the last screen.
The other thing you could do is to create your own subclass of UIViewController and not support the button event, like this:
- (IBAction)done:(id)sender
{
//inform the user, that going back is not possible, for example with UIAlertView
//[self.delegate infoViewDidFinish:self];
}
However, this solution seems a bit odd, because the user expects a existing button to work.
Still, this would work.
Others have given answers that present modal view controllers or build a navigation stack. In most cases I would use one of these approaches. Yet, the simplest way to fix the code in the question is to just remove the menu view from the super view. Something like this:
- (IBAction)returnToHome:(id)sender{
[self.view removeFromSuperview];
}
In order to speed up my app, I've create three different UIViewController in AppDelegate and it has readonly property for the controllers. Those controllers are used for navigation controller.
If I tap a button on the root view, I just show another view using pushViewController method. Let me show you some code for this here.
UIViewController* controller = delegate.anotherViewController;
[delegate.navigationController pushViewController:controller animated:YES];
At first time, this work well, but if I navigate back and tap the button again, I've got a signal 'EXC_BAD_ACCESS' at second line.
What's wrong? And, how can I prepare all of my view controllers at the beginning, not create them when they are needed?
Most of the time EXC_BAD_ACCESS means that you've released an object and you're trying to reuse it without retaining it.
Look if you have released your viewController too early and whether you are (re)using it the right way or not...
I had the same problem. My code was
AddMedia *info = [[AddMedia alloc] initWithStyle:UITableViewStyleGrouped];
[self.navigationController pushViewController:info animated:YES];
[info release];
I was releasing my viewCOntroller which was crashing the app.
When I commented that line, It worked seamlessly. The code after the change is:
AddMedia *info = [[AddMedia alloc] initWithStyle:UITableViewStyleGrouped];
[self.navigationController pushViewController:info animated:YES];
// [info release];
So I'm trying to pop a view controller off the stack when an error occurs, but it seems like it's popping too much off in one go. The navigation bar up the top loses its title and buttons, but the old table view data remains visible. I have no idea what's going on...
The basic set up is:
Tab View template
Navigation controller
View controller (Loaded from the xib)
View controller (Pushed, what I want to pop)
Here's the code:
NSLog(#"%#", [[self navigationController] viewControllers]);
[[self navigationController] popViewControllerAnimated:NO];
NSLog(#"%#", [[self navigationController] viewControllers]);
The resulting NSLog's show:
2009-09-22 19:57:14.115 App[34707:550b] (
<MyViewController: 0xd38a70>,
<MyViewController: 0xd36b50>
)
2009-09-22 19:57:14.115 App[34707:550b] (null)
Anyone have experience with this?
I am seeing some odd UINavigationController stack behavior with just using
[self.navigationController popViewControllerAnimated:YES];
inside of a delegate called out of a tableView:didSelectRowAtIndexPath: call.
The views don't work right, and a UINavigationController provided "back" operation doesn't correctly pop the view of this delegate, going back another level.
I found that if I used
[self.navigationController popToViewController:self animated:YES];
instead, that suddenly everything worked fine. This is in an application with ARC turned on.
So, I can only guess that there is some reference housekeeping that doesn't happen correctly unless you tell it to pop back to a specific view controller when you will pop a view controller that will immediately become "unreferenced" by that pop.
I fixed it. The code that was popping the view was getting called in viewDidLoad. This meant it was getting popped before the view had actually animated in completely.
I moved that code to viewDidAppear and now it works as advertised.
[[self navigationController] popViewControllerAnimated:NO];
//here you pop **self** from navigation controller. And now
[self navigationController] == nil;
// And
[nil viewControllers] == nil
Try to do this:
UINavigationController *nc = [self navigationController];
NSLog(#"%#", [nc viewControllers]);
[nc popViewControllerAnimated:NO];
NSLog(#"%#", [nc viewControllers]);
What is wrong with?
[self.navigationController popViewControllerAnimated:NO];
Also, you may want to check how you push the ViewController onto the stack. Something doesn't sound right.