How to subclass UINavigationBar for a UINavigation

2019-01-07 06:07发布

问题:

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.

回答1:

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.



回答2:

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.



回答3:

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).



回答4:

- (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.



回答5:

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;
}


回答6:

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.