以编程方式在UINavigationController中设置UINavigationBar的自定义子类

时间:2009-12-08 19:37:17

标签: objective-c uinavigationcontroller uinavigationbar

如果我以编程方式(没有IB)实例化UINavigationBar,有谁知道我如何使用UINavigationController的自定义子类?

拖动UINavigationController在IB中显示我在导航栏下并使用Identity Inspectory我可以更改类类型并设置我自己的UINavigationBar子类但是我不能编程navigationBar导航控制器的属性是只读...

如何以编程方式自定义导航栏? IB比“代码”更“强大”吗?我相信在IB中可以做的所有事情也可以通过编程方式完成。

12 个答案:

答案 0 :(得分:88)

您只需使用KVC就不需要使用XIB。

[self.navigationController setValue:[[[CustomNavBar alloc]init] autorelease] forKeyPath:@"navigationBar"];

答案 1 :(得分:65)

从iOS5开始,apple提供了一种直接执行此操作的方法。 Reference

UINavigationController *navigationController= [[UINavigationController alloc]initWithNavigationBarClass:[CustomNavBar class] toolbarClass:nil];
[navigationController setViewControllers:[NSArray arrayWithObject:yourRootViewController]];

答案 2 :(得分:37)

从iOS 4开始,您可以使用UINib类来帮助解决此问题。

  1. 创建自定义UINavigationBar子类。
  2. 创建一个空的xib,添加UINavigationController作为单个 宾语。
  3. UINavigationController的{​​{1}}的类设置为自定义子类。
  4. 通过以下方法之一设置根视图控制器:
    • UINavigationBar
    • [navController setViewcontrollers[NSArray arrayWithObject:myRootVC]];
  5. 在代码中:

    [navController pushViewController:myRootVC];


    现在,您的自定义UINib *nib = [UINib nibWithNibName:@"YourCustomXib" bundle:nil]; UINavigationController *navController = [[nib instantiateWithOwner:nil options:nil] objectAtIndex:0]; 已获得UINavigationController

答案 3 :(得分:26)

据我所知,有时确实有必要继承UINavigationBar,做一些非标准的重新定位。有时可以通过使用categories来避免这样做,但并非总是如此。

目前,据我所知,在UIViewController中设置自定义UINavigationBar的 only 方式是通过IB(即通过存档) - 它可能不应该那样,但就目前而言,我们必须忍受它。

这通常很好,但有时使用IB并不可行。

所以,我看到了三个选项:

  1. 将UINavigationBar子类化并在IB中将其全部挂起,然后在每次我想要UINavigationController的时候搞砸加载nib,
  2. 在类别中使用method replacement来更改UINavigationBar的行为,而不是子类化,或
  3. UINavigationBar的子类,并对UINavigationController的归档/取消归档做了一些讨论。
  4. 在这种情况下,选项1对我来说是不可行的(或者至少太烦人了),因为我需要以编程方式创建UINavigationController,2在我看来有点危险并且更多是最后的选择,所以我选择了选项3。

    我的方法是创建一个UINavigationController的“模板”存档,并将其归档,并将其返回initWithRootViewController

    以下是:

    在IB中,我创建了一个UINavigationController,其中包含适用于UINavigationBar的类。

    然后,我使用现有的控制器,并使用+[NSKeyedArchiver archiveRootObject:toFile:]保存了它的存档副本。我只是在app delegate中,在模拟器中完成了这个。

    然后我使用'xxd'实用程序和-i标志,从保存的文件生成c代码,将存档版本嵌入到我的子类(xxd -i path/to/file)中。

    initWithRootViewController内,我取消归档该模板,并将self设置为unarchive的结果:

    // This is the data from [NSKeyedArchiver archivedDataWithRootObject:controller], where
    // controller is a CTNavigationController with navigation bar class set to CTNavigationBar,
    // from IB.  This c code was created using 'xxd -i'
    static unsigned char archived_controller[] = {
        0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xd4, 0x01, 0x02, 0x03,
        ...
    };
    static unsigned int archived_controller_len = 682;
    
    ...
    
    - (id)initWithRootViewController:(UIViewController *)rootViewController {
         // Replace with unarchived view controller, necessary for the custom navigation bar
         [self release];
         self = (CTNavigationController*)[NSKeyedUnarchiver unarchiveObjectWithData:[NSData dataWithBytes:archived_controller length:archived_controller_len]];
         [self setViewControllers:[NSArray arrayWithObject:rootViewController]];
         return [self retain];
    }
    

    然后,我可以抓住我的UIViewController子类的一个新实例,它具有自定义导航栏集:

    UIViewController *modalViewController = [[[CTNavigationController alloc] initWithRootViewController:myTableViewController] autorelease];
    [self.navigationController presentModalViewController:modalViewController animated:YES];
    

    这给了我一个模态UITableViewController,其中包含一个导航栏和工具栏,并且具有自定义导航栏类​​。我不需要做任何稍微讨厌的方法替换,当我真的只想以编程方式工作时,我不必使用nibs。

    我希望在UINavigationController中看到+layerClass的等价物 - +navigationBarClass - 但是现在,这是有效的。

答案 4 :(得分:5)

我使用“选项1”

创建一个只包含UINavigationController的nib文件。并将UINavigationBar类设置为我的自定义类。

self.navigationController = [[[NSBundle mainBundle] loadNibNamed:@"navigationbar" owner:self options:nil] lastObject];

[navigationController pushViewController:rootViewController animated:YES];

答案 5 :(得分:5)

Michael的解决方案有效,但您可以避免使用NSKeyedArchiver和'xxd'实用程序。只需子类UINavigationController并覆盖initWithRootViewController,直接加载自定义NavigationController NIB:

- (id) initWithRootViewController:(UIViewController *)rootViewController
{
    [self release];
    self = [[[[NSBundle mainBundle] loadNibNamed:@"CTNavigationController" owner:nil options:nil] objectAtIndex:0] retain];  
    [self setViewControllers:[NSArray arrayWithObject:rootViewController]];
    return self;
}

答案 6 :(得分:4)

更新:使用object_SetClass()不再像iOS5 GM一样有效。下面添加了另一种解决方案。

使用NSKeyedUnarchiver手动设置导航栏的unarchive类。

   MyViewController *controller = [[[MyViewController alloc] init] autorelease];
   NSKeyedUnarchiver *unarchiver = [[[NSKeyedUnarchiver alloc] initForReadingWithData:[NSKeyedArchiver archivedDataWithRootObject:controller]] autorelease];
   [unarchiver setClass:[MyNavigationBar class] forClassName:@"UINavigationBar"];
   controller = [unarchiver decodeObjectForKey:@"root"];




注意:此原始解决方案仅适用于iOS5之前:

有一个很棒的解决方案,我发布了here - 将navBar子类直接注入您的视图UINavigationController

#import <objc/runtime.h>

- (void)viewDidLoad {
    [super viewDidLoad];

    object_setClass(self.navigationController.navigationBar, [MyNavBar class]);
    // the rest of your viewDidLoad code
}

答案 7 :(得分:1)

我发现我们需要使用子类而不是类别的一种情况是使用模式图像设置导航栏backgroundcolor,因为在iOS5中使用类别覆盖drawRect不再起作用。如果你想支持ios3.1-5.0,你唯一能做的就是子类化导航栏。

答案 8 :(得分:1)

这些类别方法很危险,不适合初学者。 iOS4和iOS5的复杂性也不同,这使得这个区域可能会导致很多人出错。这是我使用的一个简单的子类,支持iOS4.0~iOS6.0,非常简单。

<强>·H

@interface XXXNavigatioNBar : UINavigationBar
@end

<强>的.m

#import "XXXNavigationBar.h"

#import <objc/runtime.h>

@implementation XXXNavigationBar

- (void) didMoveToSuperview {
    if( [self respondsToSelector: @selector(setBackgroundImage:forBarMetrics:)]) {
        //iOS5.0 and above has a system defined method -> use it
        [self setBackgroundImage: [UIImage imageNamed: @"nav-bar"]
                   forBarMetrics: UIBarMetricsDefault];
    }
    else {
        //iOS4.0 requires us to override drawRect:. BUT!!
        //If you override drawRect: on iOS5.0 the system default will break,
        //so we dynamically add this method if required
        IMP implementation = class_getMethodImplementation([self class], @selector(iOS4drawRect:));
        class_addMethod([self class], @selector(drawRect:), implementation, "v@:{name=CGRect}");
    }
}

- (void)iOS4drawRect: (CGRect) rect {
    UIImage* bg = [UIImage imageNamed:@"nav-bar-blue"];
    [bg drawInRect: rect];
}

@end

答案 9 :(得分:0)

不推荐来继承UINavigationBar类。自定义导航栏的首选方法是设置它的属性,使其按照您的意愿显示,并在UIBarButtonItems中使用自定义视图以及委托以获得所需的行为。

你想做什么需要子类化?

另外,我不认为IB实际上正在替换导航栏。我很确定它不会显示默认值,并将您的自定义导航栏作为子视图。如果你调用UINavigationController.navigationBar,你会得到一个bar的实例吗?

答案 10 :(得分:0)

除了obb64的评论之外,我最终使用setViewControllers:animated:的技巧将控制器设置为从笔尖加载的rootController的{​​{1}}。这是我正在使用的代码:

navigationController

答案 11 :(得分:0)

如果您想将navBar子类化为仅更改背景图像 - 则无需在iOS 5中使用。 会有像这样的方法 setBackgroundImage