多UIWindow下的statebar颜色问题的探究

最新项目的用到了改变statebar文字颜色。特别是我的项目是支持iOS9以后的系统,白色和黑色切换


WX20190416-202201.png

可能你觉得没什么,但是我说的是因为所以的问题。探究下苹果内部的实现。和解决一下多UIWindow的APP stateBar文字颜色问题。
先来说一下单个UIWindow窗口下 窗口statebar .网上很多的解决方法。
我的项目是支持iOS9以后的系统,基本上


@property(readwrite, nonatomic) UIStatusBarStyle statusBarStyle NS_DEPRECATED_IOS(2_0, 9_0, "Use -[UIViewController preferredStatusBarStyle]") __TVOS_PROHIBITED;
- (void)setStatusBarStyle:(UIStatusBarStyle)statusBarStyle animated:(BOOL)animated NS_DEPRECATED_IOS(2_0, 9_0, "Use -[UIViewController preferredStatusBarStyle]") __TVOS_PROHIBITED;

// Setting statusBarHidden does nothing if your application is using the default UIViewController-based status bar system.
@property(readwrite, nonatomic,getter=isStatusBarHidden) BOOL statusBarHidden NS_DEPRECATED_IOS(2_0, 9_0, "Use -[UIViewController prefersStatusBarHidden]") __TVOS_PROHIBITED;
- (void)setStatusBarHidden:(BOOL)hidden withAnimation:(UIStatusBarAnimation)animation NS_DEPRECATED_IOS(3_2, 9_0, "Use -[UIViewController prefersStatusBarHidden]") __TVOS_PROHIBITED;

像这样的方法都被废弃掉了。即使 你使用也会不起作用。
唯有一下方法可用:

#if UIKIT_DEFINE_AS_PROPERTIES
@property(nonatomic, readonly) UIStatusBarStyle preferredStatusBarStyle NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; // Defaults to UIStatusBarStyleDefault
@property(nonatomic, readonly) BOOL prefersStatusBarHidden NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; // Defaults to NO
// Override to return the type of animation that should be used for status bar changes for this view controller. This currently only affects changes to prefersStatusBarHidden.
@property(nonatomic, readonly) UIStatusBarAnimation preferredStatusBarUpdateAnimation NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; // Defaults to UIStatusBarAnimationFade
#else
- (UIStatusBarStyle)preferredStatusBarStyle NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; // Defaults to UIStatusBarStyleDefault
- (BOOL)prefersStatusBarHidden NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; // Defaults to NO
// Override to return the type of animation that should be used for status bar changes for this view controller. This currently only affects changes to prefersStatusBarHidden.
- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; // Defaults to UIStatusBarAnimationFade
#endif

网上很多代码说了使用这几个方法解决;我是是这样实现的
1.子类化UINavigationController

@implementation BBBaseNavigationVC
@synthesize refreshStatusBarStyle = _refreshStatusBarStyle;

#pragma mark - Lazy

-(void)setRefreshStatusBarStyle:(UIStatusBarStyle)refreshStatusBarStyle {
    if (_refreshStatusBarStyle != refreshStatusBarStyle) {
        _refreshStatusBarStyle = refreshStatusBarStyle;
        [self setNeedsStatusBarAppearanceUpdate];
    }
    
}
- (UIStatusBarStyle)preferredStatusBarStyle {
    
    return _refreshStatusBarStyle;
    
}

提示共一个刷新方法
2.在BaseViewController的viewWillAppear调setRefreshStatusBarStyle去刷新

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [self.navigationController setNavigationBarHidden:NO animated:animated];
    [self.navigationController.navigationBar setBackgroundImage:nil forBarMetrics:UIBarMetricsDefault];
    self.navigationController.navigationBar.shadowImage = nil;
    self.navigationController.navigationBar.barTintColor = NavBarColor;
    [(BBBaseNavigationVC *)self.navigationController  setRefreshStatusBarStyle:UIStatusBarStyleLightContent];
    
}

基本网上都是说有UINavigationController情况都要去刷新UINavigationController的- (UIStatusBarStyle)preferredStatusBarStyle的方法,但是并没说为什么。
其实我也是这样做的。并没深入去看。因为手里有其他工作去做。
--------------------------------分割线----------------------------------------
这里就要说多UIWindow的问题了。
因为我用的是一体机跟硬件的调试离得很远。因为种种原因。我要来会跑查看控制台日志,很是麻烦就写了一个打印日志的东西。
大概代码如下:
BBLogTool.h

@interface BBLogTool : NSObject
+ (instancetype)sharedManager;
@property(nonatomic,assign)BOOL hidden;
- (void)initialization;
-(void)diplayLogStr:(NSString *)logStr;
@end

BBLogTool.m

@interface BBLogTool ()
@property(nonatomic,strong)BBLogVC *logVC;
@property(nonatomic,strong)BBLogWindow *window;
@end

@implementation BBLogTool

+ (instancetype)sharedManager {
    
    return [[self alloc] init];
}

- (void)initialization {
    
    self.logVC = [BBLogVC new];
    BBLogWindow *window = [BBLogWindow window];
    self.window = window;
    window.windowLevel = UIWindowLevelNormal;
    window.rootViewController = self.logVC;
    [window makeKeyAndVisible];
    self.hidden = YES;
    
}

多余代码就贴出来了,下面也是;
BBLogWindow.m

// 私有方法,全别用来上传app staore
+(UIWindow *)_findWindowForControllingOverallAppearance {
    
    return [UIApplication sharedApplication].delegate.window;
}

BBLogVC 没什么跟这次说的东西有关的,就不贴了,效果基本上市这样子的。
手势可穿透,不会影响正常的交互


多UIWindow下的statebar颜色问题的探究_第1张图片
WX20190416-205503.png

可是遇到一个,问题就是原来单UIWindow的设置statebar不起作用了。耽误我了好久时间,废话不多说;

@implementation BBBaseNavigationVC
@synthesize refreshStatusBarStyle = _refreshStatusBarStyle;

#pragma mark - Lazy

-(void)setRefreshStatusBarStyle:(UIStatusBarStyle)refreshStatusBarStyle {
    if (_refreshStatusBarStyle != refreshStatusBarStyle) {
        _refreshStatusBarStyle = refreshStatusBarStyle;
        [self setNeedsStatusBarAppearanceUpdate];
    }
    
}

不管我怎么去setNeedsStatusBarAppearanceUpdate都不管用了。没办法。我直接打断点进去看看他怎么实现的。汇编代码:

UIKitCore`-[UIViewController setNeedsStatusBarAppearanceUpdate]:
->  0x10fd9f3f0 <+0>:   pushq  %rbp
    0x10fd9f3f1 <+1>:   movq   %rsp, %rbp
    0x10fd9f3f4 <+4>:   pushq  %r15
    0x10fd9f3f6 <+6>:   pushq  %r14
    0x10fd9f3f8 <+8>:   pushq  %r13
    0x10fd9f3fa <+10>:  pushq  %r12
    0x10fd9f3fc <+12>:  pushq  %rbx
    0x10fd9f3fd <+13>:  pushq  %rax
    0x10fd9f3fe <+14>:  movq   %rdi, %r15
    0x10fd9f401 <+17>:  movq   0x12b66a8(%rip), %rdi     ; UIViewController._parentModalViewController
    0x10fd9f408 <+24>:  addq   %r15, %rdi
    0x10fd9f40b <+27>:  callq  0x1108e4722               ; symbol stub for: objc_loadWeakRetained
    0x10fd9f410 <+32>:  movq   %rax, %rbx
    0x10fd9f413 <+35>:  testq  %rax, %rax
    0x10fd9f416 <+38>:  je     0x10fd9f42d               ; <+61>
    0x10fd9f418 <+40>:  movq   0x126c669(%rip), %rsi     ; "setNeedsStatusBarAppearanceUpdate"
    0x10fd9f41f <+47>:  movq   %rbx, %rdi
    0x10fd9f422 <+50>:  callq  *0xe41088(%rip)           ; (void *)0x000000010bf12640: objc_msgSend
    0x10fd9f428 <+56>:  jmp    0x10fd9f4b4               ; <+196>
    0x10fd9f42d <+61>:  movq   0x12b65fc(%rip), %rdi     ; UIViewController._parentViewController
    0x10fd9f434 <+68>:  addq   %r15, %rdi
    0x10fd9f437 <+71>:  callq  0x1108e4722               ; symbol stub for: objc_loadWeakRetained
    0x10fd9f43c <+76>:  movq   %rax, %r14
    0x10fd9f43f <+79>:  testq  %rax, %rax
    0x10fd9f442 <+82>:  je     0x10fd9f456               ; <+102>
    0x10fd9f444 <+84>:  movq   0x126c63d(%rip), %rsi     ; "setNeedsStatusBarAppearanceUpdate"
    0x10fd9f44b <+91>:  movq   %r14, %rdi
    0x10fd9f44e <+94>:  callq  *0xe4105c(%rip)           ; (void *)0x000000010bf12640: objc_msgSend
    0x10fd9f454 <+100>: jmp    0x10fd9f4ab               ; <+187>
    0x10fd9f456 <+102>: leaq   0x131b6ab(%rip), %rax     ; UIApp
    0x10fd9f45d <+109>: movq   (%rax), %rdi
    0x10fd9f460 <+112>: movq   0x126d611(%rip), %rsi     ; "_findWindowForControllingOverallAppearance"
    0x10fd9f467 <+119>: movq   0xe41042(%rip), %r13      ; (void *)0x000000010bf12640: objc_msgSend
    0x10fd9f46e <+126>: callq  *%r13
    0x10fd9f471 <+129>: movq   %rax, %rdi
    0x10fd9f474 <+132>: callq  0x1108e4758               ; symbol stub for: objc_retainAutoreleasedReturnValue
    0x10fd9f479 <+137>: movq   %rax, %r12
    0x10fd9f47c <+140>: movq   0x125d63d(%rip), %rsi     ; "rootViewController"
    0x10fd9f483 <+147>: movq   %rax, %rdi
    0x10fd9f486 <+150>: callq  *%r13
    0x10fd9f489 <+153>: movq   %rax, %rdi
    0x10fd9f48c <+156>: callq  0x1108e4758               ; symbol stub for: objc_retainAutoreleasedReturnValue
    0x10fd9f491 <+161>: movq   %rax, %r13
    0x10fd9f494 <+164>: movq   %rax, %rdi
    0x10fd9f497 <+167>: callq  *0xe4101b(%rip)           ; (void *)0x000000010bf0f990: objc_release
    0x10fd9f49d <+173>: movq   %r12, %rdi
    0x10fd9f4a0 <+176>: callq  *0xe41012(%rip)           ; (void *)0x000000010bf0f990: objc_release
    0x10fd9f4a6 <+182>: cmpq   %r15, %r13
    0x10fd9f4a9 <+185>: je     0x10fd9f4cb               ; <+219>
    0x10fd9f4ab <+187>: movq   %r14, %rdi
    0x10fd9f4ae <+190>: callq  *0xe41004(%rip)           ; (void *)0x000000010bf0f990: objc_release
    0x10fd9f4b4 <+196>: movq   %rbx, %rdi
    0x10fd9f4b7 <+199>: addq   $0x8, %rsp
    0x10fd9f4bb <+203>: popq   %rbx
    0x10fd9f4bc <+204>: popq   %r12
    0x10fd9f4be <+206>: popq   %r13
    0x10fd9f4c0 <+208>: popq   %r14
    0x10fd9f4c2 <+210>: popq   %r15
    0x10fd9f4c4 <+212>: popq   %rbp
    0x10fd9f4c5 <+213>: jmpq   *0xe40fed(%rip)           ; (void *)0x000000010bf0f990: objc_release
    0x10fd9f4cb <+219>: leaq   0x131b636(%rip), %rax     ; UIApp
    0x10fd9f4d2 <+226>: movq   (%rax), %rdi
    0x10fd9f4d5 <+229>: movq   0x1270c94(%rip), %rsi     ; "_updateCurrentStatusBarViewControllerAppearance"
    0x10fd9f4dc <+236>: jmp    0x10fd9f44e               ; <+94>

用OC代码实现大概如下,没用的我就不写出来了:

-(void)setNeedsStatusBarAppearanceUpdate {
    
    UIViewController *x = self.parentViewController;
    
    if (x) {
        // 基本上相当于 [parentViewController  setNeedsStatusBarAppearanceUpdate],回到开始
        [x setNeedsStatusBarAppearanceUpdate];
    } else {
        
        UIWindow *winow = [UIApp _findWindowForControllingOverallAppearance];
        if (winow.rootViewController == self) {
            [UIApp _updateCurrentStatusBarViewControllerAppearance];
        }
    }
    
}

这里就看明白就是为什么只会在UINavigationController起作用。
也许你会说UINavigationController的parentViewController明明是UITabbarCOntroller,你这解释不通。解决这个迷惑帮你:
拦截-(UIViewController *)childViewControllerForStatusBarStyle在UITabbarCOntroller的实现,当着这个不为nil时会调动自己- (UIStatusBarStyle)preferredStatusBarStyle方法,只因为UITabbarCOntroller 内部实现childViewControllerForStatusBarStyle方法
汇编如下:

UIKitCore`-[UITabBarController childViewControllerForStatusBarStyle]:
->  0x10b53140d <+0>:  pushq  %rbp
    0x10b53140e <+1>:  movq   %rsp, %rbp
    0x10b531411 <+4>:  movq   0x13a53f8(%rip), %rax     ; UITabBarController._selectedViewController
    0x10b531418 <+11>: movq   (%rdi,%rax), %rdi
    0x10b53141c <+15>: popq   %rbp
    0x10b53141d <+16>: jmp    0x10c166752               ; symbol stub for: objc_retainAutoreleaseReturnValue

OC 代码大概如下:

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

所以一把不要重写UITabBarController的childViewControllerForStatusBarStyle方法。不谈就麻烦了。所以只要重新UINavigationController的大多方法就可以了。所以UINavigationController的实现satebar 能从两方面入手,一个实现childViewControllerForStatusBarStyle这个,一个不实现这childViewControllerForStatusBarStyle,实现preferredStatusBarStyle。她俩只能有一个起作用。
----------------------------------分割线--------------------------------------
大概说了单一UIWIndow下的实现和为什么实现。多UIWIndow可能又点小不一样。我APP的UI层次如下:


多UIWindow下的statebar颜色问题的探究_第2张图片
WX20190416-213649.png

从setNeedsStatusBarAppearanceUpdate汇编代码看出他最终要执行的找到一个UIWIndow,判断执行 [UIApp _updateCurrentStatusBarViewControllerAppearance];。这里我们注意下,我打断点最后发现[UIApp _findWindowForControllingOverallAppearance]在多UIWIndow下居然是BBLogWindow。然后BBLogWindow.rootController去执行那玩意,最后statebar文字变化居然交给了BBLogWindow.rootController去实现了。。。坑。
为了让我写的BBLogWindow这个小玩意,能想一个工具一下,不影响APP,不需要在我真正的APP代码中去调用,像Reveal一样。我实现了BBLogWindow的+(UIWindow *)_findWindowForControllingOverallAppearance 方法:

#ifdef DEBUG
+(UIWindow *)_findWindowForControllingOverallAppearance {
    
    return [UIApplication sharedApplication].delegate.window;
}
#endif

这个方法用分类(Category)的方法重载;
这次探究虽然很累但是我至少知道了之所以然。
与其像工具一样重复不如多学点东西。偶尔在公司开玩笑说“学XXX,加入XXX”。(玩笑话,希望不会被开)

你可能感兴趣的:(多UIWindow下的statebar颜色问题的探究)