UINavigationController setViewController:animated: navigation bar difficulties - iphone

I'm having an incredibly frustrating problem that appears to be a bug, but I have a very hard time believing no one else has come across this. My application's root view controller is a UITabBarController, where each tab is a UINavigationController. Everything works great.
Now I've come to a place where I want to edit the stack, so I rearrange the viewControllers of the current navigation controller and then do:
[self.navigationController setViewControllers:newViewControllers animated:YES];
The stack is correctly popped/pushed to the top view controller, but the navigation bar does not update to the current view controller and seems to remain exactly as it did with the viewController before the pop. If I do:
[self.navigationController popToViewController:someViewController animated:YES];
Everything works perfectly. Has anyone ever come across this before? Is there a workaround? Something I'm doing wrong?

I faced the same problem, it seems that Apple haven't corrected that bug and as a result the selected answer of this thread appears to be incorrect.
I managed to correct this problem using this bug report as in the comment of Anurag combined with the comment of Scott Pfeil.
Here is the code :
navController.navigationBarHidden = YES;
NSArray* viewControllers = navController.viewControllers;
UIViewController* currentController = [viewControllers objectAtIndex:viewControllers.count-1];
NSArray *controllers = [NSArray arrayWithObjects: viewController , currentController , nil];
[navController setViewControllers:controllers animated:NO];
navController.navigationBarHidden = NO;
I call this code in the viewDidLoad of the currentController and what I did is replace the previous controllers with only viewController.
Hope this helps.

Apple appears to have fixed this in the newest SDK

Two equally ugly work arounds.
First,
If:
[self.navigationController popToViewController:someViewController animated:YES];
Works well, try pushing an extra viewcontroller on the stack and then call:
[self.navigationController popToViewController:someViewController animated:NO];
Meaning you should get to the vc you want without any animation.
Second,
Before setting the stack, set the leftButtonBarItem = nil;
Effectively removing the old view controller's button. In fact if the title is wrong, change that too.
Neither is exactly clean but may get you the desired results.

You can also set your root view controller as the UINavigationController's delegate like:
#interface YourViewController : UIViewController <UINavigationControllerDelegate> {
and then in the didShowViewController delegate method you manually set the available view controllers:
-(void)navigationController:(UINavigationController*)navigationController didShowViewController:(UIViewController*)viewController animated:(BOOL)animated {
[[viewController navigationController] setViewControllers:[[viewController navigationController] viewControllers]];
}
Let me know if this works in your environment!

I'm still facing this issue in the Xcode 9.4.1 & iOS 11.4 .
The easiest way is to call loadViewIfNeeded() for all previous view controllers in the navigation stack:
let menuViewController = ...
menuViewController.loadViewIfNeeded()
let submenuViewController = ...
navigationController.setViewControllers([menuViewController, submenuViewController], animated: true)

[self.navigationController setViewControllers:newViewControllers animated:NO];
this may help you.

Related

PresentViewController not presenting the view

-(void) switchtodetail{
LocorecoDetailViewController *detail_view_controller = [[LocorecoDetailViewController alloc] init];
[self presentViewController:detail_view_controller animated:YES completion:nil];
}
Above code doesn't present detail_view_controller. More info about LocorecoDetailViewController, it is the detail view controller template generated automatically when using Master-Detail application template
So I have a controller called SearchController which presents a modal view to add a question. Searchcontroller is the one which has switchtodetail function. Switchtodetail presents a modal controller, to add a question. After the question is added, I need to present a new view controller (detail view controller) So the flow is SearchController -> Add Question (Modal) -> after adding back to Searchcontroller -> LocorecoDetailViewcontroller. Last link is the broken one.
If above two does not work, try this :
-(void) switchtodetail{
LocorecoDetailViewController *detail_view_controller = [[LocorecoDetailViewController alloc] initWithNibName:#"LocorecoDetailViewController" bundle:nil];
[self presentModalViewController:detail_view_controller animated:YES];
}
I usually use this to present view controller. I think one of these three definitely help you to resolve your problem. All the best!
use:
[self presentModalViewController:detail_view_controller animated:YES];
Hope this will work.
Hi please specify the nib name which you want to load ,so change the line
LocorecoDetailViewController *detail_view_controller = [[LocorecoDetailViewController alloc] init];
to
LocorecoDetailViewController *detail_view_controller = [[LocorecoDetailViewController alloc] initWithNibName:#"LocorecoDetailViewController" bundle:nil];
And also remove the completion:nill part from the next code

Can I not 'pushModalViewController'?

I have a UIViewController class and a second which I want to push modally preferably. However I can't seem to call [self pushModalViewController:...], how come?
What requirements do I need to meet to be able to do so?
I am doing this and getting a black view pushed:
vc = [[ViewController alloc] init];
[vc setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
[self presentModalViewController:vc animated:YES];
I have made my view controller in my storyboard and given it a custom class. I am trying to present this view modally via this class as seen in my code.
Any help much appreciated, thanks.
'push' and 'modal' don't belong together in the same thought. You can:
present a modal view controller, preferably using -presentViewController:animated:completion:, which is the modern replacement for -presentModalViewController:animated:
push a view controller onto the navigation stack, assuming that you're using a UINavigationController. To do that from a view controller, use:
[self.navigationController pushViewController:foo animated:YES];
You're actually looking for [self presentModalViewController:myViewController.view animated:YES]
How are you initing this viewcontroller. From the posted code, I assume it is being initialized to a blank view.
Maybe you can init it from a nibname or something?
vc = [[ViewController alloc] initWithNibName:#"NibName" bundle:nil];
[vc setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
[self presentModalViewController:vc animated:YES];
Also another thing to note, if this is a custom class and no nib file, is there any code in initWithCoder? How does the viewDidLoad looks like?

Switching Views gives me a SIGABRT Message

I'm having a little predicament switching between views, here.
Alright, so, I have this view controller class in my iPhone project called "BaseViewController," which is the default, which has a button called "GoToNextView." I added another ViewController to the storyboard called "NextViewController," and then I created another custom view controller class called "NextViewController." Under the inspector window for NextViewController on the storyboard I changed its custom class to "NextViewController;" I'm assuming everything should be hooked up, now. When I click on the "GoToNextView" button, though, the application stops with a SIGABRT message.
Here's the code for my button click action in the BaseViewController class.
- (IBAction)Transition_Next:(id)sender
{
nextViewController = [[NextViewController alloc]
initWithNibName:#"SecondView"
bundle:[NSBundle mainBundle]];
[self.view addSubview:nextViewController.view];
}
What might I be doing wrong, here?
Thanks in advance...
You can use:
- (IBAction)Transition_Next:(id)sender
nextViewController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:nextViewController animated:YES];
}
Instead of your code and it should work! Hope that helps!
You can use [self.navigationController pushViewController:nextViewController animated:NO]; or if you don't have a navigation controller, you may use
[self presentModalViewController:nextViewController animated:NO]; instead of [self.view addSubview:nextViewController.view];

NavigationController becomes nil after popToViewController

My navigationcontroller becomes nil after my "[self.navigationController popToViewController: [self.navigationController.viewControllers objectAtIndex:0] animated:YES];"
this is my scene:
InsertViewController - > [self.navigationController pushViewController:choiceViewController animated:YES];
ChoiceViewController -> [self.navigationController pushViewController:choiceDetailViewController animated:YES];
ChoiceDetailViewController ->
InsertViewController *insertViewController = [self.navigationController.viewControllers objectAtIndex:0] ;
UINavigationController *secondaryNavigationCtrl = [[UINavigationController alloc] initWithRootViewController:insertViewController];
secondaryNavigationCtrl.navigationBar.barStyle = UIBarStyleBlackOpaque;
[self presentModalViewController:secondaryNavigationCtrl animated:YES];
[secondaryNavigationCtrl release];
[
When "ok" button pressed( self.navigationItem.leftBarButtonItem) in InsertView that just poped up, then it goes back to ChoiceDetailViewController and i do a
[code][self.navigationController dismissModalViewControllerAnimated:YES]; [/code]
After that i do a
[self.navigationController popToViewController: [self.navigationController.viewControllers objectAtIndex:0] animated:YES];
Which go back tot the InsertViewController, and when i do the cycle again i see my navigationcontroller is nil...
Any idea what I am doing wrong?
Thanks in advance.
I'm not sure I've understand what you are trying to do but, when you go back to ChoicheDetailViewController i think you should do a [self.navigationController popViewController:...] instead of [self.navigationController dismissModalViewControllerAnimated:YES] because ChoicheDetailViewController got pushed on the stack in a non modal way in the first place. So with your code you are actually dismissing the whole navigationController.
Not sure if this fixes your problem but I had this problem because the class that implemented the code that popped the view controller was actually getting popped. This caused my self.navigationController to be nil because it itself was getting removed. I moved the code to a class that was not getting popped and it did not get set to nil.
Before the stack looked like this, one of the ViewControllers gets popped self.navigationController is nil.
PopableViewController
functionThatCallsPopToViewController
PopableViewController
functionThatCallsPopToViewController
RootViewController
After
PopableViewController
PopableViewController
RootViewController
functionThatCallsPopToViewController
Since RootViewController is not popped self.navigationController did not get set to nil. The only tricky thing is, now you need to keep a reference to the RootViewController in your other viewControllers.

UINavigationController popToRootViewController, and then immediately push a new view

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.