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


92

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

UINavigationController在IB中拖动一个,在导航栏下显示一个,然后使用Identity Inspectory我可以更改类类型并设置自己的子类,UINavigationBar但是通过编程我无法设置,navigationBarNavigation Controller的属性为只读...

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


您是否有运气在其他地方找到解决方案?
prendio2

你有任何答案吗?
Hrushikesh Betai 2012年

Answers:


89

您无需仅仅使用KVC就可以与XIB混为一谈。

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

此解决方案的工作原理很吸引人(至少在iOS 5.1上如此)。所有其他解决方案似乎都需要更多工作。仍在寻找不利之处。
丹尼尔(Daniel)

6
您如何找到导航控制器的此关键路径@“ navigationBar”。你能分享这个吗
伊克巴尔·汗

您确定这不会导致App拒绝吗?我实际上没有在任何地方看到此文件。
Bani Uppal

谢谢!在iOS 6 beta 3中也很好用!@BaniUppal KVC当然已经记录在案,我们只是将它与很难找到的密钥一起使用。我认为这是重点。
约翰内斯隆德

9
这似乎是
骇人听闻的

66

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

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

:@nonamelive其实不是,在iOS6的增加developer.apple.com/library/ios/#releasenotes/General/...
Pascalius

7
是的,它是在iOS 6中添加的,但在iOS 5中也受支持。一位苹果工程师在WWDC 2012 Session 216中提到了这一点
。–

37

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

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

在代码中:

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


现在UINavigationController,您的自定义项有了UINavigationBar


除此之外,您如何设置rootViewController?
2011年

您使用[navcontroller setViewControllers:[NSArray arrayWithObject:<YOUR_ROOT_CONTROLLER>]]
柯尼贝雷(Coryybeare)2011年

抱歉,您使用的是[navcontroller pushViewController:<YOUR_ROOT_CONTROLLER>动画:否]
cubeybeare 2011年

3
恕我直言,这是进行这种有时不可避免的黑客攻击的最少“骇客”方法。
David Pisoni

2
实际上,这是一个奇怪的问题。当我这样做时,新的navigationController的navigationItem未设置为分配给我推送到(空)堆栈的viewController的navigationItem属性。
David Pisoni

26

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

目前,据我所知,在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:]。我只是在模拟器的应用程序委托中完成的。

然后,我将'xxd'实用程序与-i标志一起使用,从保存的文件生成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,其中所有导航栏和工具栏都已设置,并且自定义导航栏类​​就位。我不需要做任何令人讨厌的方法替换,并且当我真的只想以编程方式工作时,我不必为笔尖而烦恼。

我想看到+layerClassUINavigationController中的等效项+navigationBarClass--但现在可以了。


5

我使用“选项1”

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

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

[navigationController pushViewController:rootViewController animated:YES];

因此,笔尖文件的适当名称将为loadNibNamed:@“ navigationController吗?实际上,您是从笔尖而不是从酒吧中获取整个navigationController。对吗?
aneuryzm 2011年

这确实对我有用,但是当我单击表格视图中的任何选项时,我会丢失后退按钮。
jfisk

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

4

更新:使用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之前的版本:

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

#import <objc/runtime.h>

- (void)viewDidLoad {
    [super viewDidLoad];

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

正如我在上一个答案中指出的那样,您将其发布在-iOS5的黄金大师打破了这一点。不过,还有其他选项可用,因此我将使用其他方法进行编辑。
2011年

1

我发现需要使用子类而不是类别的一种情况是使用图案图像设置导航栏背景色,因为在iOS5中,使用类别覆盖drawRect不再起作用。如果要支持ios3.1-5.0,唯一的方法是对Navigationbar进行子类化。


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

0

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

您想做什么需要子类化?

另外,我不认为IB实际上会取代导航栏。我敢肯定,它只是不显示默认值,而是将您的自定义导航栏作为子视图。如果调用UINavigationController.navigationBar,是否获得栏的实例?


2
嗨,本,谢谢。我知道不是继承UINavigationBar的更好方法,但是我想设置覆盖drawRect:方法的背景图像。问题是,使用IB可以在UINavigationController中更改导航栏的类,但不能以编程方式更改。是的,IB实际上正在替换导航栏:NSLog(@“%@”,self.navigationController.navigationBar); <CustomNavigationBar:0x1806160; baseClass = UINavigationBar; 帧=(0 20; 320 44); clipsToBounds = YES; 不透明=不;autoresize = W; 层= <CALayer:0x1806da0 >>
Duccio

我也使用了同样的技术,它继续在iOS5中工作(不同于UINavigationBar Category技术。)
David Pisoni


0

除了obb64的评论,我最终使用了他的把戏,setViewControllers:animated:将控制器设置rootControllernavigationController从笔尖加载的。这是我正在使用的代码:

- (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];
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.