I just realised that my 'root' viewController should have been a UINavigationController. Now I want to change this to be a UINavigationController instead and just curious what my best option would be. I built this view and all other views using IB if that makes a different.
I'm mostly worried that I would have to do a lot of copy/pasting and recoding to get everything right or will be it be as easy as manually editing my controller and change the extension to UINavigationController.
Thanks
You do not convert an existing view controller into a navigation controller.
Even though UINavigationController is a subclass of UIViewController, it's task is the management of other view controllers, not the management of views themselves. You don't swap them out one for the other. Instead, you set UIViewControllers to be controlled by the nav.
To add a nav to a project in IB, open the xib and drag over a UINavigationController. Then set the navigation controller's rootControlller property to the existing UIViewController.
And you're done.
You can also "Embed" the Navigation Controller by selecting the View Controller in IB then select Editor->Embed In->Navigation Controller
You can instantiate a trivial not-subclassed UINavigationController and give it your original UIViewController as its root controller, like:
YourRootViewController *rootViewController = [[YourRootViewController] initHoweverYouInitIt];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
and maybe an [rootViewController release];, depending on how you are going to manage memory.
UINavigationController has an 'is-a' relationship with UIViewController, so you should be able to change its class type in Interface Builder with no additional changes.
Related
My app's storyboard is using UIViewController's to go to different views of the app. However, I want to try a third party library, that is EGOPhotoViewer, not to reinvent the wheel. But how do I add UINavigationController to UIViewController from the storyboard? Here is the code this library is using to initialize.
EGOPhotoViewController *photoController = [[EGOPhotoViewController alloc] initWithPhotoSource:source];
[self.navigationController pushViewController:photoController animated:YES]
It only works for me when I add it as a view controller:
[self presentModalViewController:photoController animated:YES];
but the library works best within navigation controller because title bars and navigation buttons are missing from my testing approach.
In the storyboard
select your original viewController, then in the menu:
Editor -> embed in -> Navigation Controller (that viewController becomes the rootViewController)
Now you have various options to push your photoController eg:
From a UI widget in your rootViewController, CTRL-drag to photoController. That will create a segue which should work without extra code (although it helps to name the segue so that you can refer to it later in code)
or in code as you have in the question.
I have UITabbarMoreController which contains a few UINavigationControllers. These UINavigationControllers then contain my custom view controllers.
So the hierarchy looks something like this:
UITabbarController
- UINavigationController
-> my custom UIViewController
- ...(other children of UITabbarController look the same)
From within my custom view controller I call [self parentViewController] which should return a UINavigationController. And this really happens, I really do get a UINavigationController, BUT only when the particular UINavigationController is NOT inside moreNavigationController.
Since I have many children controllers in the tabbar controller, the tabbar controller creates a moreNavigationController. If I open a viewcontroller that is under moreNavigationController and call [self parentViewController] it returns a mysterious object with class UIMoreNavigationController
I really need to get the UINavigationController that is parent of my view controller, not the UIMoreNavigationController. Also, I tried using [self navigationController] with the same result. How can I get reference to the real closest parent of my viewcontroller? Thanks for help in advance!
In short, you can't.
Apple always try to optimise their code. One of their optimisation here is to check whether the ViewController displayed in the list of its UIMoreNavigationController is of type UINavigationController. Because UIMoreNavigationController itself is a UINavigationController it does not want to create another UINavigationController. It's trying to avoid UINavigationController nesting.
When you look at the header file of UIMoreNavigationController you will notice it has a variable
UINavigationController* _originalNavigationController; which is accualy the original UINavigationController that you created and that you want to access. Unfortunetly you cant since UIMoreNavigationController is private.
Workaround (ugly)
Pass the reference of your NavigationController to its children when you push them on it's stack.
I got a tabbed application like this:
and already set up everything like it should look, but it won't function yet. I already googled my problem and they said you first need to set up a NavigationController with the table view as rootView and then the NavigationBar but I really couldn't figure it out. Hope someone of you can help me.
Based on your response to my comment on your question here is what you should be doing:
First off, in order to make it look like the settings app table, you will need to change the style of your UITableView to UITableViewStyleGrouped.
Your hierarchy will consist of the following:
The viewcontroller that is actually added into your UITabBarController viewControllers array(since I see you have a tabbar as your lowest level of navigation) should be an UINavigationController. The root viewcontroller of the navigation controller should be the uiviewcontroller subclass you made that contains your table view. (let's say it's called SettingsViewController)
SettingsViewController *settingsViewController = [[SettingsViewController alloc] init];
UINavigationController *settingsNavController = [[UINavigationController alloc] initWithRootViewController:settingsViewController];
You will probably need to create a different UIViewController subclass for each type of detail pane you're going to want (if they have different functionality).
In the didSelectRowAtIndexPath UITableViewDelegate function, you will create the appropriate detail viewcontroller and push it onto your navigation stack.
Let's say you have a volume settings view controller as an example. The following is the code you would have in the function I just mentioned. Keep in mind you also need to actually check the index and/or section of the selected row to figure out which detail view should be shown.
VolumeSettingsViewController *volumeSettings = [[VolumeSettingsViewController alloc] init];
[self.navigationController pushViewController:volumeSettings animated:YES];
By default, this will function pretty much like the Apple Settings app navigation. The navigation bar will automatically have a "back" button to take you back to the settings view.
If you are using a Storyboard, select your view controller, go to the "Edit" menu and choose, "Embed in Navigation Controller."
If not using story boards, assuming this will be done in code, you need to create things in a reverse order of their hierarchy - something like this:
Create an instance of the Einstellungen tab's TableViewController using initWithNibName:
Create a UINavigationController using initWithRootViewController: and setting the Einstellungen as the root
Create a UITabBarController and set your navigation controller as one of the view controllers of this tab bar controller
Add the tab bar controller as a subview to the main window in your application delegate
This will create this hierarchy:
Tab bar controller
->view controller: Navigation Controller -> root view controller: Einstellungen
Here's what I have:
A MainWindow.xib file configured with one UIViewController (subclassed to RootViewController). This nib gets loaded at application launch.
RootViewController has two ivars, a custom subclass of UIViewController and a UINavigationController. Both of these are loaded from nibs.
When the application launches both ivars are initialized from their respective nibs, and then the UIViewController.view is added as a subview of RootViewController.view.
Inside UIViewController's view I have a control that triggers an animated swap of UIViewController and UINavigationController. This is where the problem comes in. The swap animates, but the UINavigationController's views are not properly displayed. I get a Navigation Bar with no title, and nothing else.
The UINavigationController nib and underlying functionality have been tested in a stand alone project, but there was no RootViewController.
So, my question is, can I even do this? I've successfully swapped other view controllers like this, but never a UINavigationController. I've seen some documentation that leads me to believe this might be a fools errand, but I haven't convinced myself of that yet.
Solution (Kinda):
I found a solution (workaround? hack?), but it leads to some more questions. I nixed using a Nib for the UINavigationController. Instead, I loaded my UINavigationController's rootViewController from a Nib and then created the UINavigationController programmatically with initWithRootViewController:.
NavRootViewController *navRoot = [[NavRootViewController alloc] initWithNibName:#"NavRootViewController" bundle:nil];
navigationController = [[UINavigationController alloc] initWithRootViewController:navRoot];
[navRoot release];
This works as I expect. Which leads me to the conclusion that the rootViewController property of the UINavigationController wasn't being set properly when I loaded navigationController from a Nib. And the question is, why? Should it?
Also, when you see something like this happening in one case, but not another, it can be beneficial to either create a subclass and make your nib point at that subclass, or if you already have a subclass use that.
In the subclass, override all the various init:, initWithNibName:bundle:, viewDidLoad:, viewWillAppear:, viewDidAppear: and any other appropriate methods, and in those override, just NSLog("") something about which method it is (with param values perhaps) and call the super implementation.
This will give you an observable "track" of which methods are called in which order, and you can set a breakpoint to see where that call comes from.
This will give you enough information to find missing method calls, and then you can pursue the correct problem either here, or through filing a radar or ...
In some cases, viewDidLoad and viewDidAppear or awakeFromNib may need to be called each time you add the UINavigationController back into the stack of UIViewControllers. It seems that when the typical code executes out of your AppDelegate, that the Window, or something behind the scenes is doing something special for UINavigationController that presentModalViewController doesn't do.
I think you may have missed a conceptual point.
A UINavigationController controls view controllers instead of views. It controls when and where view controllers themselves are loaded. The views themselves are loaded only as a side effect of the pushing and popping of their respective controllers.
Therefore, putting a navigation controller inside of a view controller seldom makes much sense.
If I understand what you are trying to do correctly, you should have the RootController actually set as the rootController property of the UINavigationController (yes the nomenclature is extremely confusing.) Then when your swap event occurs, you should have the navigation controller push the next view. The RootController view will disappear to replaced by the other. then you can repeat the process for an arbitrary number of view controllers.
Only in the case of a tabbar would you want a navigation controller to be a property of a view controller. Even then it should be at the top the tab's hierarchy.
I think I've found the cause: Document Info window in IB has a warning: "'Selected Navigation Controller (Second)' has nib name property set to 'SecondView.nib', but this view controller is not intended to have its view set in this manner."
Bummer.
I've built nib in Interface Builder that has UITabBarController at top level and switches between UINavigationControllers.
It works fine when everything is in a single nib file, but I'd like to use separate nib files for UINavigationControllers.
Starting with Apple's TabBar template, if I just change class of SecondView to UINavigationController, it all breaks:
and all I get is this:
// imgur has lost the image, sorry //
Is it possible to have separate file for UINavigationController without programmatically setting everything?
I would like TabBarController to handle loading and unloading of nibs.
Simply swap the UINavigationController with the FirstViewController.
So the hierarchy should be like this:
Tab bar controller
-----Tab bar
-----Navigation Controller
----------First View Controller
---------------Navigation Item
----------Tab bar item (First)
-----Navigation Controller
----------Second View Controller
---------------Navigation Item
----------Tab bar item (Second)
You set the nib of First View Controller in the inspector to the nib file containing the actual view objects (Since you are trying to split them into separate files, which is a good thing).
You have one tab, that tab has a navigation controller which loads First View Controller as its root view.
Done.
I haven't tried setting up UINavigationController via IB. I have multiple screens, each is stored in separate xib and there's a corresponding class that extends UIViewController. In applicationDidFinishLaunching I initialize UIViewControllers using xib's but then manually create UINavigationController, add navigation controller's view to window and push first view to navigation controller.
Not sure if that helps.
- (void)applicationDidFinishLaunching:(UIApplication *)application {
navigationController = [[UINavigationController alloc] init];
FirstViewController * viewController = [[FirstViewController alloc]
initWithNibName:#"FirstView"
bundle:nil];
[navigationController pushViewController:viewController animated:NO];
[viewController release];
[window addSubview:navigationController.view];
[window makeKeyAndVisible];
}
Above FirstViewController extends UIViewController, in IB you create your view then set File's owner class to your class (e.g. here FirstViewController) and connect the File's owner view to the UIView's view.
I believe you are looking for something like this. You would replace "whatever" with the name of you second nib file.
newNavController = [[UINavigationController alloc] initWithNibName:#"whatever" bundle:[NSBundle mainBundle]];
First, it looks like you have your UITabBarItems under the navigation controllers instead of directly under the UITabBarController. That may be part of your problem.
Second, when you add a UITabBarController in IB and and click on its icon in your list of top-level objects (your first screenshot), the attributes inspector will allow you to change the type of view controller for each of the tabs. Using this, you can change them all to navigation controllers, if you wish. Also, since you wanted to load custom views and view controllers from other nibs, if you look at the "View Controller" section at the bottom of the attributes inspector, you can select a nib from your project to load the view from. Assuming that nib's "File's Owner" is set to your UINavigationController subclass, it should all work fine.
All of this without a large amount of coding work, either. Let me know if you'd like screenshots for what I'm talking about in case you can't find these panels.
I found the same warning.I have kept all view controller in separate xib files. I got rid off it by removing .nib name and keeping it empty.