93

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

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

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

4

12 回答 12

89

您不需要与 XIB 混为一谈,只需使用 KVC。

[self.navigationController setValue:[[[CustomNavBar alloc]init] autorelease] forKeyPath:@"navigationBar"];
于 2012-03-07T23:33:23.347 回答
67

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

UINavigationController *navigationController= [[UINavigationController alloc]initWithNavigationBarClass:[CustomNavBar class] toolbarClass:nil];
[navigationController setViewControllers:[NSArray arrayWithObject:yourRootViewController]];
于 2012-09-21T08:48:42.913 回答
37

As of iOS 4, you can use the UINib class to help solve this issue.

  1. Create your custom UINavigationBar subclass.
  2. Create an empty xib, add a UINavigationController as the single object.
  3. Set the class for the UINavigationController's UINavigationBar to your custom subclass.
  4. Set your root view controller via one of these methods:
    • [navController setViewcontrollers[NSArray arrayWithObject:myRootVC]];
    • [navController pushViewController:myRootVC];

In code:

UINib *nib = [UINib nibWithNibName:@"YourCustomXib" bundle:nil];
UINavigationController *navController = 
             [[nib instantiateWithOwner:nil options:nil] objectAtIndex:0];


Now you've got a UINavigationController with your custom UINavigationBar.

于 2011-05-25T00:19:26.797 回答
26

据我所知,有时确实有必要将 UINavigationBar 子类化,以进行一些非标准的重新设计。有时可以通过使用categories来避免这样做,但并非总是如此。

目前,据我所知,在 UIViewController 中设置自定义 UINavigationBar 的唯一方法是通过 IB(即通过存档)——它可能不应该这样,但现在,我们必须接受它。

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

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

  1. 子类 UINavigationBar 并将其全部连接到 IB 中,然后每次我想要一个 UINavigationController 时都想加载笔尖,
  2. 在类别中使用方法替换来改变 UINavigationBar 的行为,而不是子类化,或者
  3. 子类 UINavigationBar 并在归档/取消归档 UINavigationController 方面做一些处理。

在这种情况下,选项 1 对我来说是不可行的(或者至少太烦人了),因为我需要以编程方式创建 UINavigationController,在我看来,2 有点危险,而且更像是最后的选择,所以我选择了选项 3。

我的方法是创建 UINavigationController 的“模板”存档,然后取消存档,将其返回为initWithRootViewController.

就是这样:

在 IB 中,我创建了一个 UINavigationController,并为 UINavigationBar 设置了适当的类。

然后,我使用现有的控制器,并使用+[NSKeyedArchiver archiveRootObject:toFile:]. 我只是在模拟器中的应用程序委托中执行此操作。

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

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

// 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 烦恼。

我想+layerClass在 UINavigationController中看到等价物+navigationBarClass- 但现在,这可行。

于 2010-06-20T19:33:27.553 回答
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;
}
于 2011-10-20T12:57:19.713 回答
5

我使用“选项1”

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

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

[navigationController pushViewController:rootViewController animated:YES];
于 2010-12-17T00:53:11.327 回答
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 之前:

有一个很棒的解决方案,我在这里发布了——将 navBar 子类直接注入到您的视图中UINavigationController

#import <objc/runtime.h>

- (void)viewDidLoad {
    [super viewDidLoad];

    object_setClass(self.navigationController.navigationBar, [MyNavBar class]);
    // the rest of your viewDidLoad code
}
于 2011-10-06T19:27:20.263 回答
1

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

于 2011-11-30T23:31:01.617 回答
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
于 2012-10-01T08:35:16.547 回答
0

Further to obb64's comment, I ended up using his trick with setViewControllers:animated: to set the controller as the rootController for the navigationController loaded from the nib. Here's the code I'm using:

- (void) presentModalViewControllerForClass: (Class) a_class {
  UINavigationController *navController = [[[NSBundle mainBundle] loadNibNamed: @"CustomNavBar" owner:self options:nil] lastObject];

  LoginSignupBaseViewController *controller = [[a_class alloc] initWithNibName: nil bundle: nil];
  controller.navigationController = navController;
  [navController setViewControllers: A(controller) animated: NO];

  [self presentModalViewController: navController animated: YES];

  [controller release];
}
于 2011-02-02T23:15:13.343 回答
0

建议对类进行子UINavigationBar类化。自定义导航栏的首选方法是设置它的属性以使其按您的意愿显示,并使用 UIBarButtonItems 中的自定义视图以及委托来获得所需的行为。

你想做什么需要子类化?

另外,我认为 IB 实际上并没有取代导航栏。我很确定它根本没有显示默认导航栏,而是将您的自定义导航栏作为子视图。如果你调用 UINavigationController.navigationBar,你会得到你的 bar 的实例吗?

于 2009-12-08T19:45:34.927 回答
0

如果你想继承 navBar 只是为了改变背景图像 - 在 iOS 5 中不需要。会有这样的方法setBackgroundImage

于 2011-08-09T13:41:57.850 回答