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

Posted

技术标签:

【中文标题】以编程方式在 UINavigationController 中设置 UINavigationBar 的自定义子类【英文标题】:Set a custom subclass of UINavigationBar in UINavigationController programmatically 【发布时间】:2010-12-24 13:39:56 【问题描述】:

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

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

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

【问题讨论】:

您是否有幸在其他地方找到了解决方案? 你有什么答案吗? 【参考方案1】:

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

你想做什么需要子类化?

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

【讨论】:

嗨,本,谢谢。我知道子类化 UINavigationBar 不是更好的方法,但我想设置一个覆盖 drawRect: 方法的背景图像。问题是使用 IB 我可以在 UINavigationController 中更改导航栏的类,但以编程方式我不能。是的,IB 实际上正在替换导航栏: NSLog(@"%@", self.navigationController.navigationBar); > 我也在使用同样的技术,并且它在 ios5 中继续工作(与 UINavigationBar 类别技术不同。)【参考方案2】:

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

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

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

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

    子类 UINavigationBar 并将其全部连接到 IB 中,然后每次我想要一个 UINavigationController 时都要加载 nib, 在类别中使用method replacement 来更改UINavigationBar 的行为,而不是子类化,或者 子类 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,其中包含所有设置的导航栏和工具栏,以及自定义导航栏类​​。我不需要做任何有点讨厌的方法替换,当我真的只是想以编程方式工作时,我也不必为 nib 乱七八糟。

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

【讨论】:

【参考方案3】:

我使用“选项 1”

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

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

[navigationController pushViewController:rootViewController animated:YES];

【讨论】:

所以 nib 文件的适当名称应该是 loadNibNamed:@"navigationController ?您实际上是从 nib 获取整个 navigationController 而不仅仅是 bar。对吧? 这对我有用,但是当我点击表格视图中的任何选项时,我会丢失后退按钮。【参考方案4】:

除了 obb64 的评论,我最终使用他的技巧与 setViewControllers:animated: 将控制器设置为 rootController 用于从笔尖加载的 navigationController。这是我正在使用的代码:

- (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];

【讨论】:

【参考方案5】:

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

    创建您的自定义UINavigationBar 子类。 创建一个空的xib,添加一个UINavigationController作为单 目的。 将UINavigationControllerUINavigationBar 的类设置为您的自定义子类。 通过以下方法之一设置您的根视图控制器: [navController setViewcontrollers[NSArray arrayWithObject:myRootVC]]; [navController pushViewController:myRootVC];

在代码中:

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

现在您已经有了一个带有自定义 UINavigationBarUINavigationController

【讨论】:

除此之外如何设置rootViewController? 你使用 [navcontroller setViewControllers:[NSArray arrayWithObject:]] 对不起,你用你用 [navcontroller pushViewController: animated:NO] 恕我直言,这是制造这种有时不可避免的黑客行为的最不“黑客”的方式。 其实,有一个奇怪的问题。当我这样做时,新的 navigationController 的 navigationItem 没有被设置为分配给我正在推入(空)堆栈的 viewController 的 navigationItem 属性。【参考方案6】:

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

【讨论】:

您需要使用setBackgroundImage:forBarMetrics:,如下所述:developer.apple.com/library/IOS/#documentation/UIKit/Reference/…【参考方案7】:

更新: 使用 object_SetClass() 不再像 iOS5 GM 一样工作。下面添加了一个替代解决方案。

使用 NSKeyedUnarchiver 手动设置导航栏的取消归档类。

   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

【讨论】:

正如我在您发布的上一个答案中指出的那样——iOS5 的金牌大师打破了这一点。不过还有其他选项可用,所以我将使用另一种方法进行编辑。【参考方案8】:

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;

【讨论】:

【参考方案9】:

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

【讨论】:

【参考方案10】:

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

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

【讨论】:

这个解决方案就像一个魅力(至少在 iOS 5.1 上)。所有其他解决方案似乎都做得更多。仍在寻找不利因素。 你是怎么找到导航控制器的这个关键路径@"navigationBar"的。可以分享一下吗 您确定这不会导致应用被拒绝吗?我实际上并没有在任何地方看到这个记录。 谢谢!在 iOS 6 beta 3 中也能很好地工作! @BaniUppal KVC 肯定有文档记录,我们只是将它与一个有点难以找到的密钥一起使用。我认为这是重点。 @Developer 我想他可能用过this 之类的东西,但这只是猜测。【参考方案11】:

从 iOS5 开始,苹果提供了一种方法可以直接做到这一点。 Reference

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

【讨论】:

@nonamelive 其实没有,iOS6中添加:developer.apple.com/library/ios/#releasenotes/General/… 是的,它是在 iOS 6 中添加的,但在 iOS 5 中也支持。一位 Apple 工程师在 WWDC 2012 Session 216 中提到了这一点。【参考方案12】:

这些分类方法很危险,不适合新手。此外,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

【讨论】:

以上是关于以编程方式在 UINavigationController 中设置 UINavigationBar 的自定义子类的主要内容,如果未能解决你的问题,请参考以下文章

为啥我们不能在以编程方式设置初始视图控制器时以编程方式在视图控制器之间跳转?

Android 编程:如何以网格方式以编程方式创建各种视图类型

“以编程方式”做/确定某事是啥意思? [关闭]

以编程方式在工具栏项按钮上添加图像

以编程方式设置 android:animateLayoutChanges

在 iphone 中以编程方式发送短信