How to subclass UINavigationBar for a UINavigationController programmatically?
Asked Answered
I

6

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.

Impresario answered 12/7, 2011 at 22:57 Comment(3)
Lots of people will be wondering the same thing with iOS5 submissions now opened up. Hopefully my alternative answer will make migration easier.Whaleboat
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.Understate
You should change the accepted answer to this one: https://mcmap.net/q/225308/-how-to-subclass-uinavigationbar-for-a-uinavigationcontroller-programmaticallyHummel
W
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.

Whaleboat answered 6/10, 2011 at 17:6 Comment(6)
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 5Coal
@Coal Yep. As of the GM release it no longer works. It did in the previous beta though.Whaleboat
Updating answer to include another alternative that will work with iOS5 GM.Whaleboat
PS. you can also swizzle UINavigationController's initWithRootViewController, and add solution 2 in there to make life easier.Gyrose
@AlexsanderAkers I have production code in the app store which uses solution 2. It's working of iOS5 and iOS4.Whaleboat
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: tumblr.com/tagged/nskeyedunarchiverIodous
H
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.

Hummel answered 25/9, 2012 at 21:24 Comment(6)
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.Understate
Yup, this usually happens on SO, people will ignore this answer considering there's one with 20+ upvotes :)Hummel
I have added my UINavigationController from IB. It is already initiated, is there any way I can modify my navbar?Regulable
I found a way to add our custom class from IB, https://mcmap.net/q/226085/-correct-way-of-using-uinavigationcontroller-39-s-initwithnavigationbarclass-toolbarclassRegulable
How would I then set the rootViewController of the navigationbarIndreetloire
@jsetting32, answer to your question is hereThorn
W
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.

Whaleboat answered 6/10, 2011 at 17:6 Comment(6)
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 5Coal
@Coal Yep. As of the GM release it no longer works. It did in the previous beta though.Whaleboat
Updating answer to include another alternative that will work with iOS5 GM.Whaleboat
PS. you can also swizzle UINavigationController's initWithRootViewController, and add solution 2 in there to make life easier.Gyrose
@AlexsanderAkers I have production code in the app store which uses solution 2. It's working of iOS5 and iOS4.Whaleboat
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: tumblr.com/tagged/nskeyedunarchiverIodous
B
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).

Bald answered 13/7, 2011 at 19:17 Comment(3)
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.Whaleboat
You would not add a UINavigationBar to a UITableViewController. You would add the UITableViewController to a UINavigationController, and access that navigation controllers UINavigationBar property.Bald
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.Whaleboat
E
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.

Eggleston answered 27/11, 2012 at 19:48 Comment(1)
After calling initWithNavigationBarClass just call pushViewController:rootViewController animated:NO.Despicable
M
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;
}
Meson answered 17/2, 2014 at 7:6 Comment(0)
S
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.

Seigniorage answered 22/3, 2012 at 0:15 Comment(2)
GUNavigationController Whats this Controller??Lucaslucca
Should have been CustomNavigationController -- Edited.Seigniorage

© 2022 - 2024 — McMap. All rights reserved.