关于 StatusBar 与 NavigationBar 适配的一些坑

最近在项目中遇到这样一个坑:项目中NavigationController之下的ViewControllersNavigationBar有不同的样式(纯色Translucent,完全透明、隐藏等样式)。由于NavigationBar的颜色不同,StatusBar的颜色也必须随着其颜色深浅来作出适配。

适配StatusBar

- (UIStatusBarStyle)preferredStatusBarStyle方法的半残废,在NavigationController下的ViewControllers直接使用该方法是不会执行的,因为NavigationController的特殊性,同时在查阅Apple官方文档可以重写childViewControllerForStatusBarStyle这个方法:

但是太麻烦了,并没有什么卵用。

所以,我们可以一劳永逸,写一个UINavigationController的Category来扩展此方法:

#import <UIKit/UIKit.h>

@interface UINavigationController (GBExtension)

- (UIViewController *)childViewControllerForStatusBarStyle;
- (UIViewController *)childViewControllerForStatusBarHidden;

@end
#import "UINavigationController+StatusBarStyle.m"

@implementation UINavigationController (StatusBarStyle)

- (UIViewController *)childViewControllerForStatusBarStyle {
    return self.topViewController;
}

- (UIViewController *)childViewControllerForStatusBarHidden {
    return self.topViewController;
}
@end

最重要的一步,在Info.plist文件中若有UIViewControllerBasedStatusBarAppearance这个键值对务必删除掉,因为在iOS9中已不建议使用改方式。

最后,在需要改变StatusBarStyleViewController中重写preferredStatusBarStyle方法即可生效,如果不重写,则为默认的StatusBarStyle,在项目设置中可以找到该选项:


前面说完了StatusBarStyle适配的坑,轮到了NavigationBar,由于各种各样的花式ViewControllers中要有不同的NavigationBar样式,有下面的各种方法与其利弊:

  1. 最笨的方法就是在每个ViewController中加入设置NavigationBar样式的代码

    • 优点:人人都会。
    • 缺点:工作量十分巨大,产品经历一个心血来潮说要改,分分钟弄死程序员。
  2. 写一个BaseViewConroller并实现自定义NavigationBar样式的方法,所有的ViewController都将继承于BaseViewController

    • 优点:比较符合Don't repeat your self
    • 缺点:离开了BaseViewController之后会显得非常无助,而且作为整个项目中举足轻重的基类BaseViewController,很有可能会写得很庞大臃肿,到最后谁都不敢动。

那么既要降低耦合又要实现效果,不采用继承,那么就组合方式比较合适,因此我们可以采用iOS中强大的Runtime机制,用Method Swizzling这个黑科技来无痛嵌入到项目当中,实现效果。

关于Method Swizzling,安利一下一个很好用的第三方库:Aspects。我们可以不用写在- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions这个鬼地方,避免代码臃肿,我们可以直接在NSObject+ (void)load方法中进行Swizzle:

#import <Foundation/Foundation.h>
#import <Aspects/Aspects.h>

@interface ViewControllerIntercepter : NSObject

@end
#import "GBViewControllerIntercepter.h"

@implementation ViewControllerIntercepter

#pragma mark - LifeCycle

+ (void)load {
    [super load];
    [UIViewController aspect_hookSelector:@selector(viewWillAppear:)
                              withOptions:AspectPositionAfter
                               usingBlock:^(id<AspectInfo> aspectInfo, BOOL animated){
                                   [self viewWillAppear:animated viewController:[aspectInfo instance]];
                               } error:NULL];
    [UIViewController aspect_hookSelector:@selector(viewWillDisappear:)
                              withOptions:AspectPositionAfter
                               usingBlock:^(id<AspectInfo> aspectInfo, BOOL animated){
                                   [self viewWillDisappear:animated viewController:[aspectInfo instance]];
                               } error:NULL];
    [UIViewController aspect_hookSelector:@selector(viewDidAppear:)
                              withOptions:AspectPositionAfter
                               usingBlock:^(id<AspectInfo> aspectInfo, BOOL animated){
                                   [self viewDidAppear:animated viewController:[aspectInfo instance]];
                               } error:NULL];
    [UIViewController aspect_hookSelector:@selector(viewDidDisappear:)
                              withOptions:AspectPositionAfter
                               usingBlock:^(id<AspectInfo> aspectInfo, BOOL animated){
                                   [self viewDidDisappear:animated viewController:[aspectInfo instance]];
                               } error:NULL];
}


#pragma mark Intercepter method

+ (void)viewWillAppear:(BOOL)animated viewController:(UIViewController *)viewController {
    //Do anything you want
}

+ (void)viewWillDisappear:(BOOL)animated viewController:(UIViewController *)viewController {
    //Do anything you want
}

+ (void)viewDidAppear:(BOOL)animated viewController:(UIViewController *)viewController {
    //Do anything you want
}

+ (void)viewDidDisappear:(BOOL)animated viewController:(UIViewController *)viewController {
    //Do anything you want
}

@end

使用Method SwizzlingViewController的appear和disapear方法中注入自己的方法之后,我们就可以再扩展一下ViewController加入一些返回自定义NavigationBar样式属性的方法,然后加入到Swizzle的方法里面,就可以无痛嵌入项目同时实现效果,这里我就暂时不贴代码啦,即使移除了该Method Swizzling,项目依旧可以正常运行(需要实现的效果不再生效而已)

使用Method Swizzling的另外一个好处就是当项目需要加入页面统计分析等功能(如腾讯MTA,友盟等),同样可以无痛嵌入到项目当中去,真是酸爽~