【问题标题】:Custom UINavigationBar Background自定义 UINavigationBar 背景
【发布时间】:2010-10-16 19:34:14
【问题描述】:

我一直在尝试为我的整个 NavigationBar(不仅仅是 titleView)设置自定义背景,但一直在苦苦挣扎。

我找到了这个帖子

http://discussions.apple.com/thread.jspa?threadID=1649012&tstart=0

但我不确定如何实现给出的代码 sn-p。代码是否作为新类实现?另外,我在哪里设置 UINavigationController,因为我有一个使用 NavigationView 模板构建的应用程序,所以它没有按照示例在我的根控制器中完成

【问题讨论】:

标签: iphone objective-c


【解决方案1】:

Uddhav 和 leflaw 是对的。这段代码运行良好:

@interface CustomNavigationBar : UINavigationBar
@end

@implementation CustomNavigationBar
-(void) drawRect:(CGRect)rect 
{
    UIImage *image = [UIImage imageNamed: @"myNavBarImage"];
    [image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}
@end

// this can go anywhere
+(UINavigationController*) myCustomNavigationController
{
  MyViewController *vc = [[[MyViewController alloc] init] autorelease];
  UINavigationController *nav = [[[NSBundle mainBundle] loadNibNamed:@"CustomNavigationController" owner:self options:nil] objectAtIndex:0];
  nav.viewControllers = [NSArray arrayWithObject:vc];
  return nav;
}

您必须创建 CustomNavigationController.xib 并在其中放置一个 UINavigationController 并将 navigationBar 类更改为“CustomNavigationBar”。

【讨论】:

  • 行:MyViewController *vc = [[[MyViewController alloc] init];有一个额外的 [.否则,工作得很好。
  • 谢谢。我添加了一个应该存在的自动释放调用。
  • 谢谢——这很有帮助。
  • 如果我以编程方式创建导航控制器,如何更改导航栏的类?
  • 这样,你也可以稍微改变一下它的高度吗?
【解决方案2】:

在 iOS 5.xx 中,您必须使用“外观”代理更改控件的背景和其他样式属性,例如 UINavigationBarUIToolBar 等。但是,这些不适用于 iOS 4.xx,因此为了向后兼容,您需要一个混合解决方案。

如果您想同时支持 iOS 4.xx 和 iOS 5.xx 设备(即您的 DeploymentTarget 是 4.xx),您必须小心包装对外观代理的调用,方法是在运行时检查 '外观选择器是否存在。

您可以这样做:

//Customize the look of the UINavBar for iOS5 devices
if ([[UINavigationBar class]respondsToSelector:@selector(appearance)]) {
    [[UINavigationBar appearance] setBackgroundImage:[UIImage imageNamed:@"NavigationBar.png"] forBarMetrics:UIBarMetricsDefault];
}

您还应该放弃您可能已经实施的 iOS 4.xx 解决方法。如果您已经为 iOS 4.xx 设备实现了drawRect 解决方法,正如@ludwigschubert 所述,您应该将其保留在:

@implementation UINavigationBar (BackgroundImage)
//This overridden implementation will patch up the NavBar with a custom Image instead of the title
- (void)drawRect:(CGRect)rect {
     UIImage *image = [UIImage imageNamed: @"NavigationBar.png"];
     [image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}
@end

这将使NavBar 在 iOS 4 和 iOS 5 设备中看起来相同。

【讨论】:

  • 这是同时支持 iOS 4.xx 和 5.xx 的最简洁的解决方案。
【解决方案3】:

你只需要像这样重载drawRect:

@implementation UINavigationBar (CustomImage)
- (void)drawRect:(CGRect)rect {
    UIImage *image = [UIImage imageNamed: @"NavigationBar.png"];
    [image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}
@end

【讨论】:

  • 这是“推荐”的吗?用类别覆盖它的“有效”方法?
  • 这对我很有效,直到我使用了 MPMoviePlayerViewController。显然他们使用子类 UINavigationBar 所以我的自定义栏显示在上面。我不得不使用@ardalahmet 的method below,但如果它不是UINavigationBar,则使用isMemberOfClass:[UINavigationBar class] 添加对super 的调用
  • 使用类别覆盖方法不是一个好主意。在运行时,未定义将使用哪个版本的实现。
  • Apple 特别建议不要使用这种技术。您应该继承 UINavigationBar 并重载 drawRect。然后在您的 XIB 中将您的子类指定为 UINavigationBar 的类。
  • 看起来这种方法在 iOS 5 中被破坏了。
【解决方案4】:

不建议实施类别。 iOS5 可能会缓解这个问题。但是对于旧 API,您可以 -

  1. UINavigationBar 子类化为CustomNavBar 并根据Lithium 的答案实现自定义drawRect。
  2. 对于所有基于 IB 的 UINavigationControllers,提供 CustomNavBar 作为其 UINavigationBar 的自定义类。
  3. 对于所有基于代码的UINavigationControllers。使用UINavigationController 创建一个 XIB 并执行第二步。然后在代码中提供工厂方法,从 nib 加载 UINavigationController 并提供 IBOutlet

例如。

[[NSBundle mainBundle] loadNibNamed:@"CustomNavigationController" owner:self options:nil];
UINavigationController *navController = self.customNavigationController;
navController.viewControllers = [NSArray arrayWithObject:controller]

【讨论】:

  • 太好了,这种方法在 iOS 4.2.1 和 iOS 5.0.1 上对我有用
  • 好答案 - 请注意,这也适用于从 xib 中获取视图参考(无需插座)UINavigationController * navigationController = [[[NSBundle mainBundle] loadNibNamed:@"CustomNaviagionController" owner:nil options:nil] lastObject];
【解决方案5】:

您还可以覆盖UINavigationBar 类别类中的drawLayer:inContext: 方法。在drawLayer:inContext:方法里面,你可以绘制你想要使用的背景图片。

- (void) drawLayer:(CALayer *)layer inContext:(CGContextRef)context
{
    if ([self isMemberOfClass:[UINavigationBar class]] == NO) {
        return;
    }

    UIImage *image = (self.frame.size.width > 320) ?
                        [UINavigationBar bgImageLandscape] : [UINavigationBar bgImagePortrait];
    CGContextClip(context);
    CGContextTranslateCTM(context, 0, image.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);
    CGContextDrawImage(context, CGRectMake(0, 0, self.frame.size.width, self.frame.size.height), image.CGImage);
}

作为一个完整的演示 Xcode 项目,自定义 UINavigationBar thisthis 的外观可能会有所帮助。

【讨论】:

  • 不要这样做。继承 UINavigationBar 并实现 drawRect:。将您的子类指定为 XIB 中 UINavigationBar 的类。
【解决方案6】:

在 iOS5 中实现类别不起作用,您应该使用 Uddhav Kambli 的建议,在 iOS ≤ 5 上使用 CustomNavbar。

【讨论】:

  • 在 iOS 5 测试版中,UINavigationBar、UIToolbar 和 UITabBar 的实现已更改,因此除非在子类中实现,否则不会在这些类的实例上调用 drawRect: 方法。在这些类中的任何一个类别中重新实现 drawRect: 的应用程序会发现 drawRect: 方法没有被调用。 UIKit 会进行链接检查以防止在 iOS 5 之前链接的应用程序中调用该方法,但在 iOS 5 或更高版本上不支持此设计。
  • 感谢您的回复。你认为这是为了鼓励人们进行子类化,而不是在使用类别的所有实例上全局执行 drawRect 吗?
  • 这正是我的想法。
【解决方案7】:

我刚找到这篇博文,描述这个话题很简单:http://web0.at/blog/?p=38

对我帮助很大,他们使用“drawRect”方法来自定义背景。

【讨论】:

    【解决方案8】:

    对于所有在 iOS5 中遇到 UINavigationBar 自定义背景问题的人,请在相应的 viewDidLoad 方法中执行此操作:

    #if defined(__IPHONE_5_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_5_0
    if ([self.navigationController.navigationBar respondsToSelector:@selector( setBackgroundImage:forBarMetrics:)]){
        [self.navigationController.navigationBar setBackgroundImage:[UIImage imageNamed:@"TitleBar"] forBarMetrics:UIBarMetricsDefault];
    }
    #endif
    

    请注意,在我的例子中,背景图片被命名为“TitleBar”。您可以输入任何自定义背景图片名称。

    【讨论】:

    • 由于某种原因,结束 #endif 不会位于代码括号内。抱歉#endif 出现乱码。
    • 谢谢,这行得通!并且不要忘记创建@2x 图像
    • #define 是预处理器宏。它仅在编译期间有用。因此,如果您正在为 iOS5 及更高版本进行编译,它将起作用。如果您正在为 pre-iOS5 进行编译,#ifdef 将返回 false 并且该部分代码将不会被编译。如果您跨多个 iOS 版本(包括 ios5 之前的版本)编译代码,则最好使用 #ifdef,主要是为了检查任何兼容性问题。
    • 不需要静态 #if #endif 包装器,因为您正在动态验证类是否响应选择器。
    【解决方案9】:

    您会遇到的问题是,如果您使用导航控制器,每个页面的标题都会覆盖您的自定义导航栏。如果您的导航栏包含您的应用程序的徽标或名称,这显然是不可接受的。

    您可以将导航堆栈中每个视图的标题设置为空白,但某些视图会强制使用您无能为力的标题(例如照片选择器)。因此,您可能希望创建一个与您的徽标导航栏颜色或设计相同的备用导航栏图像,但有一个空白区域为重叠的标题腾出空间。

    要随意切换导航栏图像,请向您的应用委托添加一个属性来保存导航栏图像的名称,并将上面第一个示例的第一行替换为以下两个:

        YourAppDelegate* theApp = (YourAppDelegate*)[[UIApplication sharedApplication] delegate];
        UIImage* image = [UIImage imageNamed:theApp.navBarName];
    

    然后在您将推送到导航堆栈的第一个视图控制器中,执行以下操作:

    - (void)viewWillAppear:(BOOL)animated
    {
        YourAppDelegate* theApp = (YourAppDelegate*)[[UIApplication sharedApplication] delegate];
        theApp.navBarName = @"navBar_plain";
    }
    

    然后在根视图控制器中,做同样的事情,但指定带有徽标的导航栏图像,这样当用户导航回它并且没有冲突的标题时它会被恢复。

    【讨论】:

    • 真的很好。既然你这么说,我现在正在重新考虑这个问题。谢谢!
    【解决方案10】:

    另一种方法是使用 UINavigationController 的委托。 它不需要继承/覆盖 UINavigationBar 类:

    /*
     in the place where you init the navigationController:
     fixer = [[AENavigationControllerDelegate alloc] init];
     navigationController.delegate = fixer;
    */
    
    @interface AENavigationControllerDelegate : NSObject <UINavigationControllerDelegate>
    @end
    
    @implementation AENavigationControllerDelegate
    
    #define bgImageTag 143
    
    - (void)navigationController:(UINavigationController *)navigationController 
       didShowViewController:(UIViewController *)viewController 
                    animated:(BOOL)animated
    {
         //this is for the future for implementing with the appearance api:
    
        if ([[navigationController navigationBar] respondsToSelector:@selector(setBackgroundImage:forBarMetrics:)])
        {
            static dispatch_once_t onceToken;
            dispatch_once(&onceToken, ^{
                [[navigationController navigationBar] setBackgroundImage:[UIImage imageNamed:@"header-logo-bg.png"]
                                                       forBarMetrics:UIBarMetricsDefault];
            });
     }
        else
            {
            UIImageView* imageView = (UIImageView*)[navigationController.navigationBar viewWithTag:bgImageTag];
            if(!imageView)
            {
                UIImage *image = [UIImage imageNamed:@"header-logo-bg.png"];
                imageView = [[[UIImageView alloc] initWithImage:image] autorelease];
                imageView.tag = bgImageTag;
            }
    
            [navigationController.navigationBar insertSubview:imageView atIndex:0];        
        }
    }
    
    @end
    

    https://gist.github.com/1184147

    【讨论】:

      【解决方案11】:

      在 iOS5 中,zPosition 的值(UINavigationBar's 的最深层)发生了变化。因此,如果您更改 zPosition,则旧方法有效。

      例如。

      UINavigationBar *_bar = navigationController.navigationBar;
      
      // Insert ImageView    
      UIImage *_img = [UIImage imageNamed:@"navibar.png"];
      UIImageView *_imgv = [[[UIImageView alloc] initWithImage:_img] autorelease];
      _imgv.frame = _bar.bounds;
      
      UIView *v = [[_bar subviews] objectAtIndex:0];
      v.layer.zPosition = -FLT_MAX;
      
      _imgv.layer.zPosition = -FLT_MAX+1;
      [_bar insertSubview:_imgv atIndex:1];
      

      这个脚本处理视图层,所以你应该导入QuartzCore

      【讨论】:

        【解决方案12】:

        这是一个替代解决方案,可让您使用自己的 UINavigationBar 自定义子类:

        https://gist.github.com/1253807

        【讨论】:

          【解决方案13】:

          正如 Apple 自己所说,在类别中覆盖方法是不正确的。所以自定义UINavigarionBar的背景最好的方法就是继承-(void)drawInRect:方法。

          @implementation AppNavigationBar
          - (void)drawRect:(CGRect)rect
          {
              UIImage *patternImage = [UIImage imageNamed:@"image_name.png"];
              [patternImage drawInRect:rect];
          }
          

          要使用这个自定义的UINavigationBar,它应该设置为UINavigationBarControllernavigationBar 属性。如您所知,此属性是只读的。那么应该做的是:

          - (void)viewDidLoad
          {
              [super viewDidLoad];
          
              AppNavigationBar *nav = [AppNavigationBar new];
              [self setValue:nav forKey:@"navigationBar"];
          }
          

          它适用于 iOS 5 和 4.3。

          【讨论】:

            【解决方案14】:

            你可以继承 UINavigationBar 并像这样启用它,因为 drawRect 的类别在 iOS5 中不再起作用

                navigationController = [[((^ {
                NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:[NSKeyedArchiver archivedDataWithRootObject:navigationController]];
                [unarchiver setClass:[SAPHUINavigationBar class] forClassName:@"UINavigationBar"];
                [unarchiver setClass:[UIViewController class] forClassName:NSStringFromClass([navigationController.topViewController class])];
                return unarchiver;
            })()) decodeObjectForKey:@"root"] initWithRootViewController:navigationController.topViewController];
            

            【讨论】:

              【解决方案15】:

              对于静态视图(根本没有动画),我使用默认的 iOS setBackgroundImage

              但是当我有一个动画视图(最有可能调整大小)时,我会创建一个自定义 UIImageView 并将其添加到导航栏,以便我拥有更大的灵活性

              问题是,如果你只是添加它,它会在按钮和 titleView 之上,所以我手动保存大多数子视图的副本,将它们从父视图中删除,添加我的 imageView,而不是添加所有子视图返回

              这对我有用

                  UIImageView *navBackground = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"navigationBackgroundSample.jpg"]];
                  UIView *tempTitleView = [[self.navigationBar.subviews objectAtIndex:1] autorelease];
                  [[self.navigationBar.subviews objectAtIndex:1] removeFromSuperview];
                  [self.navigationBar addSubview:navBackground];
                  [self.navigationBar addSubview:tempTitleView];
                  self.navigationBar.clipsToBounds = YES;
                  [navBackground release];
              

              在这种情况下,我没有按钮,我发现我的 titleView 位于索引 1,如果你有按钮,它们应该在 navigationBar 的 subviews 数组中的某个地方

              我不知道索引 0 是什么,我不知道这是否可以解决您也没有文本标题的情况...

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多