Pushing a navigation controller is not supported - ios

In my MainStoryBoard I want to push a viewController to the detailView but I get this error:
NSInvalidArgumentException', reason: 'Pushing a navigation controller is not supported'
I set the identifier 'JSA' ID for the viewController on the storyboard.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == 0) {
SWSJSAViewController *viewController = [[UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil] instantiateViewControllerWithIdentifier:#"JSA"];
[self.navigationController pushViewController:viewController animated:YES];
}
}

Like rmaddy said in the comments you are trying to push a navigation controller.
Navigation controllers should be presented (via presentViewController or they can be added as a childViewController) and ViewControllers should be pushed.

When you talk about pushing Navigation Controller, it is most likely that you want to present it.
Presenting UINavigationController
This is the most common way and this is what you want to do in most cases. UINavigationController cannot be pushed, it can only be presented with a new root View Controller.
MyViewController* vc = [[MyViewController alloc]
initWithNibName:#"MyController" bundle:nil];
UINavigationController *myNav = [[UINavigationController alloc] initWithRootViewController: vc];
[self presentViewController:myNav animated:YES completion:nil];
What you do here, is firstly create a UINavigationController and then set necessary UIViewController as its root controller.
Pushing UINavigationController
If you have a hierarchy of ViewControllers and you need to push view controller that contains navigation controller within, steps are:
1) Push ViewController, containing UINavigationController.
To push UINavigationController, first create a subclass of UIViewController, which will be a wrapper-/container- class for your UINavigationController and its content.
ContainerViewController* vc = [[ContainerViewController alloc] init];
2) Adding UINavigationController as a child view controller
In viewDidLoad of your container (that you have just instantiated) simply add something like this:
Objective-C
UINavigationController* myNav = [[UINavigationController alloc] initWithRootViewController: rootViewController];
[myNav willMoveToParentViewController:self];
myNav.view.frame = self.view.frame; //Set a frame or constraints
[self.view addSubview:myNav.view];
[self addChildViewController: myNav];
[myNav didMoveToParentViewController:self];
Swift 4.2+
let childNavigation = UINavigationController(rootViewController: viewController)
childNavigation.willMove(toParent: self)
addChild(childNavigation)
childNavigation.view.frame = view.frame
view.addSubview(childNavigation.view)
childNavigation.didMove(toParent: self)
What you do here is basically instantiate your navigation controller and add it as a child controller to your wrapper. That's it. You have successfully pushed your UINavigationController.

Related

Objective-C Present View Controller with its Navigation Controller

I have a view controller like in the image below:
And I am trying to present this view controller from another view controller like so:
LHPDFFile *vc = [[LHPDFFile alloc] init];
vc.previewItemURL = self->_previewItemURL;
UINavigationController *navBar=[[UINavigationController alloc]initWithRootViewController:vc];
[self presentViewController:navBar animated:YES completion:nil];
This works, however my buttons are not appearing :(
It appears that the code above is creating a Navigation Controller instead of using mine with the buttons. What am I doing wrong?
Try instantiating your view controller with the storyboard. Something like:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"YourStoryboardName" bundle:nil];
LHPDFFile *vc = (LHPDFFile *)[storyboard instantiateViewControllerWithIdentifier:#"<id of your view controller in the storyboard>"];
Calling the empty init method leads to empty instantiation of the view, because you have never mentioned that it should use this storyboard's this view controller. More details here.
You init none of your controllers from storyboard! Those buttons belongs to file view controller. You should init that controller from storyboard instead of call init.
MyViewController *vc = [[self storyboard] instantiateViewControllerWithIdentifier: #"MyViewControllerStoryBoardID"];
UINavigationController *navBar = [[UINavigationController alloc]initWithRootViewController:vc];
[self presentViewController:navBar animated:YES completion:nil];

self.navigationController is 'null' even after embed in a navigationcontroller

I want to add a navigationcontroller to an existing viewcontroller which is created using storyboard, i have embed it in a navigation controller, but the code for navigating (shown below) is not working even after embed in the navigation controller:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
EditProfileViewController *nextViewController = [storyboard instantiateViewControllerWithIdentifier:#"EPVController"];
[self.navigationController pushViewController:nextViewController animated:YES];
When i have tried to log self.navigationController, it shows null.
Update: It is fine when i am trying with presentViewcontroller , but i
want to push the viewcontroller with navigationController.
I am struggling with this for two days,Please help.
If self is a subclass of UINavigationController, you do not need to refer to the navigationController property. (Note that this won't work if EditProfileViewController is also a subclass of UINavigationController, as you can't push a UINavigationController inside a UINavigationController).
EditProfileViewController *nextViewController = [storyboard instantiateViewControllerWithIdentifier:#"EPVController"];
[self pushViewController:nextViewController animated:YES];
Otherwise, if you don't have a pre-existing navigation controller
EditProfileViewController *nextViewController = [storyboard instantiateViewControllerWithIdentifier:#"EPVController"];
[self presentViewController:nextViewController animated:YES completion:nil];

iOS - how to set a navigation bar item in view controller?

I'm new to Objective-C and I want to add a UINavigationBar on my CatrgoryBIDViewController. I have proceed UIButton in InstructionBIDViewController.m file that should navigate to CatrgoryBIDViewController. Here is the function code:
- (IBAction)proceed:(id)sender {
viewControllercat =
[[CatrgoryBIDViewController alloc]
initWithNibName:#"CatrgoryBIDViewController"
bundle:nil];
UINavigationController *nav =
[[UINavigationController alloc]
initWithRootViewController:self.viewControllercat];
//[self.navigationController pushViewController:viewControllercat animated:YES];
[self.navigationController setNavigationBarHidden:NO animated:YES];
}
But it is not setting the UINavigationBar.
You should read the documentation here to understand the way a NavigationController is working. For your case:
If your current ViewController (where your proceed-method is implemented) has a NavigationController (is child of it), you can push another ViewController onto the stack of that NavigationController with
[self.navigationController pushViewController:viewControllercat animated:YES];
In this case you do not need to initialize another NavigationController, but in your CatrgoryBIDViewController in viewWillAppear you need to make the NavigationBar visible if it was not before already with
[self.navigationController setNavigationBarHidden:NO animated:YES];
If your current ViewController does not have a NavigationController, you can not push another ViewController on top of it and can not show the NavigationBar of it (although you can create your own NavigationBar and add it to the View of the ViewController, but without the usual Navigation-behaviour embedded).
If you open your ViewController programmatically (e. g. from the AppDelegate) you are correct to do so by your call:
viewControllercat = [[CatrgoryBIDViewController alloc] initWithNibName:#"CatrgoryBIDViewController" bundle:nil];
UINavigationController *nav=[[UINavigationController alloc] initWithRootViewController:self.viewControllercat];
My apologies - After having reread your question, I am going to tweak my answer some.
-(IBAction)proceed:(id)sender
{
viewControllercat = [[CatrgoryBIDViewController alloc] init];
UINavigationController * nc =
[[UINavigationController alloc] initWithRootViewController:vc];
// We now need to display your detail view but cannot push it.
// So display modally
[self presentViewController:nc animated:YES completion:Nil];
}
The above should result in your DetailViewController - CategoryBIDViewController being displayed on top of your InstructionBIDViewController and it should have a UINavigationController in it.

Instantiate UINavigationController on button action

I have simple application with only one main view, which has 'Settings' button, and settings are tree-grouped, so I wand to present them in navigation controller. And I don't want navigationController in main view, because I don't want navigation bar there.
That's why I don't instantiate navigationController in application: didFinishLaunchingWithOptions:. And when I check self.navigationController in 'Settings' button handler, it returns nil.
So I wrote this: (I use ARC)
- (void)doSettings
{
NSLog(#"%#", self.navigationController); // prints nothing
SettingsViewController *settingsViewController = [SettingsViewController alloc] initWithStyle:UITableViewStyleGrouped];
UINavigationController *navigationController = [[UINavigationController alloc] init];
[self.view.window setRootViewController:navigationController];
[navigationController pushViewController:settingsViewController animated:YES];
}
This works, although it pushes settingsViewController without animation (don't know why).
Is this generally the correct way to do - to change rootViewController in the middle of running app?
And if yes - than when I'm done with Settings, I probably need to set rootViewController back to current viewController, as it was before I tapped 'Settings'?
I think you want to create a navigation controller that you will present modally; the following will do:
SettingsViewController* settingsViewController = [[SettingsViewController alloc] initWithStyle:UITableViewStyleGrouped];
UINavigationController *navigationController = [[UINavigationController alloc] init];
[navigationController pushViewController:settingsViewController animated:YES];
[self presentViewController: navigationController animated: YES completion:nil];
where self here is the view controller you want to trigger the modal view controller from.
since you present modally the navigation controller you can dismiss it within the code source of your settingsViewController by accessing its navigation controller:
[self.navigationController dismissViewControllerAnimated:YES completion: nil];
To answer your question setting the rootViewController is not the correct way. Present the new vc modally through the presentViewController method.
A better way is to build the navigation vc and present it over your main vc (not replace your main vc).
- (void)doSettings
{
NSLog(#"%#", self.navigationController); // prints nothing
SettingsViewController *settingsViewController = [SettingsViewController alloc] initWithStyle:UITableViewStyleGrouped];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController: settingsViewController];
[self presentViewController: navigationController animated:YES completion:^{}];
}
Your main vc might realize (maybe as a delegate) that the settings flow is complete. It can then dismiss the presented navigation controller with:
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
Alternatively, the setting flow could dismiss itself...
// somewhere in the settings vc or a vc it pushes, when we decide settings are done
self.navigationController dismissViewControllerAnimated:YES completion:^{}];
Have navigation in main view and have below line (which will hide navigation bar)
[[self navigationController] setNavigationBarHidden:YES animated:YES];
(I would say have this in viewWillAppear and viewDidLoad both, BUT in viewWillAppear is MUST).
Now in second view, to show navigation bar
[[self navigationController] setNavigationBarHidden:NO animated:YES];
Hope this will solve your problem...

What exactly does presentModalViewController do?

If I have a navigationController which is init with a root view controller MyViewController's instance.
And in that MyViewController's code
I can use
AnotherViewController *vc = [[AnotherViewController alloc] init];
[self presentModalViewController:vc animated:YES];
or
AnotherViewController *vc = [[AnotherViewController alloc] init];
[self.navigationController presentModalViewController:vc animated:YES];
I found these two works the same. Both present the modal view correctly.And I have found that the presented AnotherViewController's "parentViewController" property are all set to the navigation controller.
Why would this happen?the presentModalViewController automatically detect that the self is the subview of the navigation controller and re send the message to navigation controller?
Because MyViewController is the root view controller of the UINavigationController, it gets the convenience method of presentModalViewController: animated: by default. So when you say self.navigationController, it is referring to the same navigationController that presentModalViewController gives you. I think Apple is just trying to make it more intuitive to use the convenience method.

Resources