最新项目的用到了改变statebar文字颜色。特别是我的项目是支持iOS9以后的系统,白色和黑色切换
可能你觉得没什么,但是我说的是因为所以的问题。探究下苹果内部的实现。和解决一下多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不起作用了。耽误我了好久时间,废话不多说;
@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层次如下:
从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”。(玩笑话,希望不会被开)