3

In order to customize the aspect of the navigationBar of my UINavigationController, I've been told to subclass the UINavigationBar class.

I need to change the height of the bar, placing an image instead of the title.. etc. I can't do it just using UINavigationBar properties.

Now, my issue is, how to assign to the UINavigationController, my CustomNavigationBar instance ? I can't use UINavigationController.navigationBar because it is read-only.

The only way seems to load a xib, but subclassing UINavigationController is not reccomended by Apple, so I'm a bit confused.

pablasso
  • 2,479
  • 2
  • 26
  • 32
aneuryzm
  • 63,052
  • 100
  • 273
  • 488
  • Try these answers: http://stackoverflow.com/questions/704558/custom-uinavigationbar-background/2720196#2720196 http://stackoverflow.com/q/380507/760275 – Nicolas S Jun 18 '11 at 21:01

1 Answers1

3

Depending exactly on how custom you want the nav bar, the following may help you:

1) An image in the bar, replacing the title text. (self is a UIViewController)

UIImageView *titleImg = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"image.png"]];
self.navigationItem.titleView = titleImg;
[titleImg release];

2) A totally custom nav bar

Subview UIView (CORRECTION: UINavigationController) with a nib, using (for example, of iPhone portrait orientation) a imageview as the background (could have a gradient) and then custom buttons on the left and right side, all hooked up to your relevant responder functions. As long as you add this nav bar view as a subview to each view controller etc each time a new one is pushed to the stack, you would have overriden Apple's standard nav bar. (remember to set theirs to hidden in the app delegate)


Edit:

NavigationBar.h

@interface NavigationBar: UIViewController
{   
    IBOutlet UIImageView* navigationBarImgView; // remember to set these 4 IBOutlets as properties with (nonatomic, retain) and synthesize them.

    IBOutlet UILabel* controllerHeader;

    IBOutlet UIButton* rightButton;
    IBOutlet UIButton* leftButton;

    eController controller;

    int screenWidth;
}

NavigationBar.m

-(id)initNavigationBar: (NSString*)name bundle:(NSBundle*)bundle: (eController)controllerName: (int)inScreenWidth
{
    [super initWithNibName:name bundle:bundle];

    controller = controllerName;
    screenWidth = isScreenWidth;

    return self;
}

You can see my init here only passes in a few things - an enum which was defined in my app delegate which contains the names of all possible view controllers. We interpret this enum in my custom Navigation Bar class and switch between all the possible controllers, and then setup their buttons and titles and the actions that get fired off using that information. The screen width is important - although you may not need this if you create the bar in IB, set the size of it to fill up the allocated space for both orientations.

Keep me posted :)

Luke
  • 11,426
  • 43
  • 60
  • 69
  • @Krypton ok cool, so I can use navigationItem to set an image as title. I only need to change the bar height. You know if I can do it with properties as well ? – aneuryzm Jun 18 '11 at 22:14
  • Apple quote: "you must never directly change UIView-level properties such as the frame, bounds, alpha, or hidden properties directly." Source: http://developer.apple.com/library/ios/#documentation/uikit/reference/UINavigationBar_Class/Reference/UINavigationBar.html – Luke Jun 18 '11 at 22:20
  • @Krypton Does this your last comment mean that I necessarily need to subclass UINavigationBar, if I want to change its height? – aneuryzm Jun 19 '11 at 08:06
  • @Krypton Also, it is not clear to me the description of point (2) in your answer. More precisely: when you say "Subview UIView with a nib" you mean to subclass UINavigationBar only, right ? But then, how do I assign it to UINavigationController programmatically, given the fact that UINavigationController.navigationBar property is read only ? – aneuryzm Jun 19 '11 at 08:07
  • @Patrick I said to subclass UIView with a nib (sorry, should be UINavigationController) - not the nav bar. You can create your own one in Interface Builder with your own custom buttons, title/image header and everything, so it looks like a UINavigationBar but is not. This is the only way to change the height of the nav bar, because as Apple said you cannot modify the private ivars of a UINavigationBar - so subclassing it would fail. I will add some code to my main answer to help. – Luke Jun 19 '11 at 10:48
  • @Krypton I have no problems with your solution. The only thing is that in the Apple documentation on the second line they warn to not subclass UIViewController. Copy/pasted: "The UINavigationController class implements a specialized view controller that manages the navigation of hierarchical content. This class is not intended for subclassing." – aneuryzm Jun 19 '11 at 11:47
  • Yes, apple are referring to navigation bar subclasses - not view controllers! In this case, we are using our own nav bar and will handle it all ourselves. – Luke Jun 19 '11 at 12:39
  • @Krypton I really think they are referring to UIViewControllers subclasses instead. But I think for my case it is a good solution. – aneuryzm Jun 19 '11 at 13:02
  • But everybody makes UIViewController subclasses every day! The documentation refers to both navigation bars and controllers - in this case we are writing our own, based off a view controller. It's perfectly safe - I've done this implementation before and the app is live in the app store. Let me know how you get on if you like, and remember to vote/accept if you are happy :) – Luke Jun 19 '11 at 13:16
  • Ok, I've done it, and it works. Just one last question, the method - (UINavigationBar *)navigationBar in my CustomNavigationController is invoked many times.. do you know why ? (I'm obviously initializing the CustomNavigationController and assigning the CustomNavigationBar just once... - is maybe because the pushed controller is a UITableViewController ? – aneuryzm Jun 19 '11 at 13:25
  • Honestly I don't know - the way I used my custom navigation bar would be to init an instance of the class, then add it as a subview to your root view controller. Then, each time you call another view controller to the stack, init another custom nav bar, add it as a subview again etc, then push the view controller. I did not use the methods you are talking about - if it is getting called too many times, are there any visible errors or does something look wrong? Also, just as a quick fix you could set a bool so that it is only run once - just to see if it helps for now. – Luke Jun 19 '11 at 13:42
  • @Krypton OK. Anyway the title and leftBarButtonItem are not anymore visible on the custom bar. I'm setting them from the pushed view because they change depending on which view is pushed. Do you know maybe why ? – aneuryzm Jun 19 '11 at 13:55
  • For example: self.title = @"PushedController Title"; doesn't work anymore. Also self.navigationItem.leftBarButtonItem = myButtonItem; doesn't work anymore. – aneuryzm Jun 19 '11 at 13:55
  • You need to create a UITextView to contain your nav bar's header in the custom files (& nib) and same for the left and right buttons. Then you can set them however you like if you switch your enum in the loadView: method of the custom bar and then hook them up with your actions etc. – Luke Jun 19 '11 at 15:39
  • @Krypton Sorry, but honestly, I haven't understand a lot of the last message. Could you rephrase it ? (1) For which view should I use a nib ? The UINavigationBar ? or the UINavigationControl ? I thought I could solve all my issues programmatically. (2) From where the UITextView come from ? I mean, can't I just use navigationItem and title property to create the left button and specify the title.. as I was doing in the previous code with a standard navigationBar properties? – aneuryzm Jun 19 '11 at 20:44
  • No worries: For every view that needs a navigation bar, you will need to init and add as a subview, your custom nav bar. This will always include your nib, so it makes handling the title and buttons easier. You will need to modify your custom nav bar class files to include a UITextView so that you can set the title - I forgot to add this into the code in my above answer with all the code that's already there. – Luke Jun 19 '11 at 21:01
  • @Krypton I see now your approach. However, if I'm adding a nav bar to each view as you say, is the parent NavigationController nav bar still visible ? I mean, my views are pushed by the navigationController and the bar is owned by it (and it has the "Back" button). If I add a custom bar to each view, I'm afraid I'm going to hide the original bar and lose the back button for the navigation. Correct ? – aneuryzm Jun 20 '11 at 07:46
  • 1
    Yes, you will need to override the default navigation bar in your app delegate - set it to hidden from the very start. Each time you load a view controller of your own you will be using an instance of your custom nav bar instead. This is why you need an enum as part of the init call, so that you can switch between them (in loadView:) and then setup the required buttons yourself. – Luke Jun 20 '11 at 08:00