23

I'm using a custom drawRect function to draw on UINavigationBar across my application in iOS4, it doesn't use images, only CoreGraphics.

Since you can't implement drawRect in UINavigationBar category in iOS5, Apple is suggesting to subclass UINavigationBar.

How is it possible to replace the UINavigationBar with my subclass in UINavigationController (so it'll be compatible with iOS4 and iOS5) when the navigationBar property is read only?

@property(nonatomic, readonly) UINavigationBar *navigationBar

I'm not using XIBs in my application at all, so adding a UINavigationBar to a NIB and changing the class via InterfaceBuilder is not an option.

Cœur
  • 37,241
  • 25
  • 195
  • 267
romaonthego
  • 810
  • 1
  • 8
  • 16
  • Lots of people will be wondering the same thing with iOS5 submissions now opened up. Hopefully my alternative answer will make migration easier. – memmons Oct 06 '11 at 17:07
  • Note that while the techniques presented in the accepted answer are still valid (excluding #4), when building against the iOS 6 SDK (even while targeting iOS 5), the simplest, cleanest and officially supported method is now the one given in Javier Soto's answer. – JK Laiho Oct 23 '12 at 08:09
  • You should change the accepted answer to this one: http://stackoverflow.com/a/12591273/778889 – Javier Soto Apr 12 '13 at 21:09

6 Answers6

81

In iOS 6 they added a new method to UINavigationController that is retractively available in iOS 5 as well:

- (id)initWithNavigationBarClass:(Class)navigationBarClass
                    toolbarClass:(Class)toolbarClass;

Now you can just pass your custom class when the navigation controller is instantiated.

Javier Soto
  • 4,840
  • 4
  • 26
  • 46
  • 1
    THIS. The solutions listed in the accepted answer are still OK, but right now (late 2012 onwards) this answer needs more upvotes, since the number of people who still need to support anything below iOS 5 is diminishing rapidly. – JK Laiho Oct 22 '12 at 10:53
  • Yup, this usually happens on SO, people will ignore this answer considering there's one with 20+ upvotes :) – Javier Soto Oct 22 '12 at 17:19
  • I have added my `UINavigationController` from IB. It is already initiated, is there any way I can modify my navbar? – Sam Sep 26 '13 at 11:15
  • I found a way to add our custom class from IB, http://stackoverflow.com/a/14617951/921358 – Sam Sep 26 '13 at 11:22
  • How would I then set the rootViewController of the navigationbar – jsetting32 Oct 29 '14 at 05:20
  • @jsetting32, [answer to your question is here](http://stackoverflow.com/questions/31217984/how-do-i-resize-height-of-uinavigationbar-while-allowing-to-set-root-view-contro) – Hemang Jul 04 '15 at 08:26
42

As of iOS6, this is now quite simple to accomplish without swizzling or messing with other classes by using UINavigationControllers method initWithNavigationBarClass:toolbarClass:

- (id)initWithNavigationBarClass:(Class)navigationBarClass 
                    toolbarClass:(Class)toolbarClass;

From the docs:

Initializes and returns a newly created navigation controller that uses your custom bar subclasses.

Answer updated for iOS6.

memmons
  • 40,222
  • 21
  • 149
  • 183
  • 1
    This doesn't work with drawLayer:inContext: or drawRect: in the subclass. I verified that it is indeed getting loaded by overriding setTag, but the drawLayer:inContext is not being called on iOS 5 – coneybeare Oct 08 '11 at 17:06
  • @coneybeare Yep. As of the GM release it no longer works. It did in the previous beta though. – memmons Oct 08 '11 at 17:36
  • Updating answer to include another alternative that will work with iOS5 GM. – memmons Oct 08 '11 at 17:36
  • 2
    PS. you can also swizzle UINavigationController's initWithRootViewController, and add solution 2 in there to make life easier. – dizy Oct 16 '11 at 14:53
  • @AlexsanderAkers I have production code in the app store which uses solution 2. It's working of iOS5 and iOS4. – memmons Oct 20 '11 at 18:25
  • 1
    Warning for Solution 2: If you init UINavigationController initWithRootViewController:, the NSKeyedUnarchiver is messing with its data. Remember to send initWithRootViewController: message again to UINavigationController called controller at the end of above snippet. Source: http://www.tumblr.com/tagged/nskeyedunarchiver – Gökhan Barış Aker Jun 28 '12 at 10:34
5

The only supported way to do this in iOS 4 is to use the Interface Builder method. You don't have to use IB to do anything except set the UINavigationBar subclass (you can still do all of your view set up programmatically).

Ross Kimes
  • 1,234
  • 1
  • 12
  • 34
  • Although this does seem to be the only current way to do it -- this approach is very, very broken. For example, if you have a UITableViewController, there is nowhere to add a `UINavigationBar` from IB. – memmons Oct 06 '11 at 16:15
  • 1
    You would not add a UINavigationBar to a UITableViewController. You would add the UITableViewController to a UINavigationController, and access that navigation controllers UINavigationBar property. – Ross Kimes Oct 17 '11 at 22:17
  • Yes, if you wanted to refactor your NIB to be based inside a `UINavigationController`, then you could certainly do that. But typically, that is not how most devs set up their NIBs. There is nothing wrong with this approach, I'm just not a fan of any approach that requires me to refactor that much. – memmons Oct 19 '11 at 17:09
1
- (id)initWithNavigationBarClass:(Class)navigationBarClass toolbarClass:(Class)toolbarClass;

I faced one problem with the above method. There is a "initWithRootViewController" method to initialize UINavigationController. But, if I use "initWithNavigationBarClass" to init the UINavigationController, then there is no way I could set "rootViewController" for the UINavigationController.

This link Changing a UINavigationController's rootViewController helped me to add a rootViewController after the UINavigationController is initialized with "initWithNavigationBarClass". Basically, the trick is to subclass UINavigationController. Though I haven't tested it in IB yet, but it is working fine in code.

sudip
  • 2,781
  • 1
  • 29
  • 41
  • After calling `initWithNavigationBarClass` just call `pushViewController:rootViewController animated:NO`. – damian Jul 17 '14 at 11:56
1

Kind of an amendment to the answers above for those that still want to use initWithRootViewController. Subclass UINavigationController and then:

- (id) initWithRootViewController:(UIViewController *)rootViewController
{
    self = [super initWithNavigationBarClass:[CustomNavigationBar class] toolbarClass:nil];
    if (self)
    {
        self.viewControllers = [NSArray arrayWithObjects:rootViewController, nil];
    }

    return self;
}
Ser Pounce
  • 14,196
  • 18
  • 84
  • 169
0

Had issues with answers 2-4 in iOS 4 (from AnswerBot's answer), and needed a way to load the UINavigationController programmatically (though the NIB method worked)... So I did this:

Created a Blank XIB file (don't set file owner), add a UINavigationController (give it your custom UINavigationController's class), change the UINavigationBar to your custom class (here it's CustomNavigationBar), then create the following custom class header (CustomNavigationController.h in this case):

#import <UIKit/UIKit.h>

@interface CustomNavigationController : UINavigationController
+ (CustomNavigationController *)navigationController;
+ (CustomNavigationController *)navigationControllerWithRootViewController:(UIViewController *)rootViewController;
@end

and custom implementation (CustomNavigationController.mm)

#import "CustomNavigationController.h"

@interface CustomNavigationController ()

@end

@implementation CustomNavigationController
+ (CustomNavigationController *)navigationController
{
    NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"CustomNavigationController" owner:self options:nil];
    CustomNavigationController *controller = (CustomNavigationController *)[nib objectAtIndex:0];
    return controller;
}


+ (CustomNavigationController *)navigationControllerWithRootViewController:(UIViewController *)rootViewController
{
    CustomNavigationController *controller = [CustomNavigationController navigationController];
    [controller setViewControllers:[NSArray arrayWithObject:rootViewController]];
    return controller;
}

- (id)init
{
    self = [super init];
    [self autorelease]; // We are ditching the one they allocated.  Need to load from NIB.
    return [[CustomNavigationController navigationController] retain]; // Over-retain, this should be alloced
}

- (id)initWithRootViewController:(UIViewController *)rootViewController
{
    self = [super init];
    [self autorelease];
    return [[CustomNavigationController navigationControllerWithRootViewController:rootViewController] retain];
}
@end

Then you can just init that class instead of a UINavigationController, and you'll have your custom navigation bar. If you want to do it in a xib, change the class of UINavigationController and UINavigationBar inside of your XIB.

BadPirate
  • 25,802
  • 10
  • 92
  • 123