I'm new in iPhone development and want to ask about the navigation controller. How can I make the navigation controller fixed over the whole application, like the facebook navigation bar. It always shows the notification, friends and messages in the navigation bar.
I'm trying to put custom view in the titleView but it disappears every time the navigation push new view?
There was a similar question posted here (https://stackoverflow.com/questions/16773312/facebook-like-navigationbar), unfortunately it was closed quite quickly. I'm redeeming its intended glory here.
You can achieve a persistent navigation bar with ease, if you think of navigation in terms of a Tab based application (with hidden tabs).
Using Storyboards:
Create a new UINavigationController (Nav 1).
Set a UITabController as the root view controller of Nav 1. (Tab Bar) Note that it will have it's own navigation bar (I've labeled it "Persistent Nav Bar").
Create another UINavigationController (Nav 2).
Set your view controller of choice (My View Controller) as the root view controller of Nav 2.
Set Nav 2 as the first (tab) view controller of the Tab Bar controller.
For Nav 1: under Attributes Inspector -> Navigation Controller -> Bar visibility, make sure that the box is ticked (so that it Shows Navigation Bar).
For Nav 2: under Attributes Inspector -> Navigation Controller ->
Bar visibility, make sure that the box is un-ticked (so that it
won't show its Navigation Bar).
If you run the app, you should see the title of the tab bar visible, and the title of your view controller hidden. This gives you the basics of a persistent nav bar. You can keep PUSHing views onto the stack from My View Controller and it will remain persistent. Presenting a MODAL view will bring up a new context, so the persistence is lost. If you repeat these steps, you should be able to create the same effect for modally presented views as well.
The remainder of this answer deals with hiding the tab bar and managing the navigation bar elements.
So that's great, but how do you place your custom view in the nav bar, and how do you hide that tab bar?
Create a subclass of UITabBarController, and assign it to the Tab Bar controller in our storyboard.
Hiding the tab bar
You can hide the tab bar in all sorts of ways. If you're interested, here's more about that (How to hide uitabbarcontroller); this little snippet is adapted from my answer in that thread:
CGRect screenRect = [[UIScreen mainScreen] bounds];
float fHeight = screenRect.size.height;
if( UIDeviceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation) )
{
fHeight = screenRect.size.width;
}
for(UIView *view in self.view.subviews){
if([view isKindOfClass:[UITabBar class]]){
[view setFrame:CGRectMake(view.frame.origin.x, fHeight, view.frame.size.width, view.frame.size.height)];
}else{
[view setFrame:CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, fHeight)];
}
}
This will shift the Tab Bar out of view and resize the view to fill up the space (without animation, as if it was never there). Add this snippet to the beginning of viewDidLoad to get that bar out of the way.
Custom view in nav bar
In the viewDidLoad method of our TB subclass, you can create your custom view with buttons and add it to the navigation bar like so: [self.navigationItem setTitleView:myCustomTitleView]; Easy.
If it doesn't show up properly, make sure you define its frame before you set it as the titleView. Then after adding it, use [myCustomTitleView sizeToFit] to get it sitting snug in the Navigation Bar.
Setting the bar button items
Setting the left and right bar button items requires a small change in notation. Normally, you would set the left bar button by referencing self.navigationItem.leftBarButtonItem. This reference is actually pointing to the left bar button of the HIDDEN navigation bar. To access the VISIBLE navigation bar, use self.tabBarController.navigationItem.leftBarButtonItem. Easy!
Handling the lost bar back button item
One thing that you sacrifice with a persistent nav bar is the management of pushed views. Things like the back arrow won't show (they are appearing on the hidden navigation bar). You can overcome this by setting your TabBarController subclass as a delegate for its view controllers (which should all be UINavigationControllers).
for (UINavigationController *navController in self.viewControllers) {
navController.delegate = self;
}
Whenever any of these UINavigationControllers push another view, you can intercept this action with this delegate method:
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
In here, you can check how many view controllers are pushed:
if(navigationController.viewControllers.count > 1){
//create a custom back button here and add it to the nav bar
}else{
//set the left bar button (where the custom back button would sit) to nil
}
The custom back button can call a method in your tab bar controller subclass, which tells the currently selected view controller to pop its current view.
Is this how Facebook did it?
I can't verify that this is the way Facebook has done it (probably not), but it will achieve a similar effect. I've used it effectively in my latest app (http://www.waterboyapp.com), which was happily accepted by Apple without a hitch. I wish someone had posted this online before, hence my contribution here to save hours/days of searching.
Aside
The added bonus to this implementation (other than it's simplicity and elegance) is that you can link in multiple view controllers to the tab bar. With a for loop and a bit of creativity, you can recreate custom buttons in the navigation bar (based on the tabs) that perform the same function as the tab bar. This saves screen space (because tab bars are pretty big) and still uses the tab bar to perform your view swapping.
What yo should understand is that the UINavigationBar displayed is a property of the view controller on the top of navigation controller hierarchy. As you understand that, you can try to customize the titleView of every view controller's UINavigationBar, e.g. at viewWillAppear. You can access the navigation bar with
self.navigationController.navigationBar
where self is a loaded view controller reference.
And for the case you want to have a persistent view somewhere on the screen one of the solutions is the following: declare a property at app-delegate class, add the view directly to the window after the root view just like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// something here ...
// root view controller
[window addSubview:myRootViewController.view];
// toolbar
MyToolbar * tb = [MyToolbar new];
tb.barStyle = UIBarStyleDefault;
[tb sizeToFit];
tb.frame = CGRectMake(0, 0, 320, 70);
[window addSubview:tb];
self.globalToolBar = tb;
[tb release];
Then you are able to update the toolbar (in this case, it might be any view in general) from any view in this way:
[[[[UIApplication sharedApplication] delegate] globalToolBar] updateRightButtonItem]
So you'll need to subclass UIToolBar with MyToolbar, add some UI methods like updateRightButtonItem there, you'll probably also need to create a delegate or to handle the notifications from the toolbar catching the events on the top view controller.
This way the view of the view controllers will be animated with the navigation controller logic, but the toolbar is a window subview and isn't changed by navigation actions automatically.
this is what worked for me:
toolBar = [[UIView alloc]initWithFrame:CGRectMake(105, 20, 105, 44)];
notificationsBtn = [UIButton buttonWithType:UIButtonTypeCustom];
messagesBtn = [UIButton buttonWithType:UIButtonTypeCustom];
[notificationsBtn setImage:[UIImage imageNamed:#"notifications-icon.png"] forState:UIControlStateNormal];
[notificationsBtn setFrame:CGRectMake(35, 0, 35 , 44)];
[notificationsBtn addTarget:self action:#selector(showNotifications:) forControlEvents:UIControlEventTouchUpInside];
[messagesBtn setImage:[UIImage imageNamed:#"messages-icon.png"] forState:UIControlStateNormal];
[messagesBtn setFrame:CGRectMake(70, 0, 35 , 44)];
[messagesBtn addTarget:self action:#selector(showMessages:) forControlEvents:UIControlEventTouchUpInside];
[toolBar addSubview:notificationsBtn];
[toolBar addSubview:messagesBtn];
[_window.rootViewController.view addSubview:toolBar];
Related
I'm programmatically creating a UITableViewController class that shows a table view with a simple navigation bar (though without a UINavigationController, as there are no further levels to the table view hierarchy).
Here is the relevant code:
- (void)viewWillAppear:(BOOL)animated {
UINavigationBar *navBar = [[UINavigationBar alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 44.0f)];
[self.tableView addSubview:navBar];
}
However, the navigation bar covers most of the first table view cell, and scrolls with the whole view.
How can I fixate the navigation bar above the table view, and keep it from scrolling through code?
The problem is that you are using UITableViewController. Switch to a standard UIViewController, add the tableview delegate and datasource methods, point the tableview to those methods, and then you do what you want to do. You could also add a UIToolbar in the XIB and create it that way if you wish.
If you really want a navigation bar, then use an NSNavigationView controller.
When you use a navigation controller, it takes care of this for you, but the navigation bar is just another subview. The remedy is to frame your table view relative to the navigation bar.
self.tableView.frame = CGRectMake(0,myNavBar.frame.size.height, 320, self.view.frame.size.height-myNavBar.frame.size.height);
Set the navigation bar's translucent property to NO:
self.navigationController.navigationBar.translucent = NO;
Having a hierarchy of data doesn't really drive whether or not you need to use a UINavigationController. There are three good reasons to go ahead and just use the UINavigationController.
You get the Navigation Bar for free, and the Nav Controller handles setting the proper frame of your root view controller when you set the nav controller's root view controller property to your view controller
If you one day wake up and say, "Hey! I want to add another layer of information to my awesome app!", you don't need to make any changes to the overall design (or, at most, minimal ones).
As my comment to #danh's suggestion implies, you're immune to whatever whacky changes Apple may decide to do with regards to nav bar height.
The solution to this behavior would be to add the UINavigationBar to the Parent View Controller's view:
[self.parentViewController.view addSubview:myNavBar];
I have a view controller, but when I hide the tab bar, there is just a black space where the tab bar was. I want to have my view controller sit behind the tab bar, so when I hide it, it shows the view content. I am using a simple UITabBarController. Thanks.
You can't do this, as far as I'm aware. The problem is that the view controllers sit within the tab bar controller, not the other way around.
The way to get around this would be to change the window view for a new navigation controller without a tab bar controller, or use a modal view to show content without a tab bar controller.
The problem is that your view on your view controller isn't tall enough to accommodate the space that your tab bar occupied.
CGRect current = [[self view] frame];
CGRect tabBarFrame = [[self tabBar] frame];
CGRect newFrame = CGRectMake(current.origin.x, current.origin.y, current.size.width, current.size.height + tabBarFrame.size.height);
[[self view] setFrame:newFrame];
Something like that is probably what you want. Or you can resize it in IB.
But, I don't know why you would use a UITabBarController and then hide the tab bar... If you can't see the tab bar, you cant switch tabs... thus making the UITabBarController pretty much just a UIViewController.
I believe that if you give the view property of your UIViewController a fixed bottom margin and a flexible height then it will stretch to fill the height of containing view automatically.
mycontroller.view.autoresizingMask = UIViewAutoresizingFlexibleHeight
I have an iPhone app based around a UINavigationController with a UIToolbar at the bottom with various buttons in it that I created through the Interface Builder. When I use [navigationController pushViewController:animated:] my new view slides into place as expected but then all of the buttons are disappearing from the toolbar - the toolbar itself stays visible, it's just completely empty.
How do I get the buttons to stay put?
Here's the bit where I respond to the user pressing one of the toolbar buttons that then shows the new view:
- (IBAction)clickSettings:(id)sender {
NSLog(#"Clicked on 'Settings' button");
SettingsViewController *settingsViewController = [[SettingsViewController alloc] initWithNibName:#"Settings" bundle:nil];
[navigationController pushViewController:settingsViewController animated:YES];
}
The tool bar buttons are the property of a given view; when you push a new view on to the navigation stack, the tool bar buttons of the new view will slide in to place.
The tool bar itself seems to "belong" to the navigation controller; visibility of the toolbar is controlled by the UINavigationController toolbarHidden property, i.e.,
self.navigationController.toolbarHidden = YES;
To actually keep the toolbar from one view to the next, you can copy the toolbarItems property from one UIView to the next.
I have an app which goes through a set of screens within a navigation controller, then there is a screen with a tab controller, which one of the contained views wants to display a modal view controller that should be displayed over the top of the whole app (not full screen though).
It's all working fine, but the modal window is partially covered at the top by the navigation controller. I've tried using self / self.tabBarController / self.navigationController / self.tabBarController.navigationController to call presentModalViewController but they either don't work or still display the modal window underneath.
I've been searching for an answer to this all day, everyone else seems to have problems when it DOES overlap, not when it doesn't.
Any ideas? Thanks. (code, screenshots & video below)
- (IBAction)add:(id)sender {
// create the view
AddAttainmentController *addScreen = [[AddAttainmentController alloc] init];
// pass in a selected pupil
[addScreen setPupils:[NSMutableArray arrayWithObject:pupil]];
// add the view to a navigation controller
UINavigationController *control = [[UINavigationController alloc] initWithRootViewController:addScreen];
// place the navigation controller on the screen
[self presentModalViewController:control animated:YES];
// release at the end
[control release];
[addScreen release];
}
Screenshots: http://cl.ly/032v2k0t0N1s1m3H0511 (you can see the navigation bar as the modal window slides in) http://cl.ly/1h0o453Y3Z051P3S1S37 (the navigation bar of the modal window is covered by the original)
Video: http://cl.ly/1e2J3o1q3V1l1j470m12
It sounds like you failed to consider some of the restrictions and assumptions around using Apple's view controller classes and are getting undefined and unexpected behavior as a result.
Tab bar controllers expect to always be at the root of your controller hierarchy. From the class reference:
When deploying a tab bar interface, you must install this view as the root of your window. Unlike other view controllers, a tab bar interface should never be installed as a child of another view controller
Additionally modal view controllers (and all view controllers for that matter) are assumed to fill their window.
As mentioned in the title, how do I implement such a functionality?
I am using the code below, (in my viewDidLoad), to get the button on my Navigation Controller of my main view.
UIButton* infoButton = [UIButton buttonWithType:UIButtonTypeInfoLight];
[infoButton addTarget:self action:#selector(viewWillAppear:) forControlEvents:UIControlEventTouchUpInside];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:infoButton];
Not sure how I can get it displayed in all my views.
Each view pushed to the navigation stack has an own navigation bar. So I afraid you have to add this button to each navigation bar, when you create a view and before you push this view to the navigation stack.
You could simply add an info button to the window, instead of creating a new button for each view controller. Since the window never changes, the button will always be on screen. You only have to take care that no other views are on top of the button.
I would suggest you create an base view controller, and add the bar button to your navigationItem in viewDidLoad. Then all your view controllers subclass from the base controller, rather than the default UIViewController. This should solve your problem.