在某一个大雪纷飞的下午,某汪找到某猿。
某汪:帮我做一个状态栏上的提示条呗。
某猿:那是系统的,苹果不准并且没有开放API。
某汪:我看某某APP就有的,再说这点小事能难倒你嘛!
某猿:当然不能,我并非浪得虚名的!
于是就有了下面的故事。
众所周知,系统状态栏是通过一个windowLevel为UIWindowLevelStatusBar的UIWindow实现的。
UIApplication.h
中可以找到UIStatusBar和UIStatusBarWindow的类申明,然后就莫有了。然并卵!
[UIWindow allWindowsIncludingInternalWindows:YES onlyVisibleWindows:NO]
我们可以用一个windowLevel大于UIWindowLevelStatusBar的UIWindow来挡住系统状态栏。这样就可以实现自己的状态栏提示条了。so easy。
这里对每个iOS工程师来说都可以省略5千字,但是有2个坑需要注意。
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event; // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event; // default returns YES if point is in bounds
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
if (self.view.y == 20) {
self.view.y = 0;
self.view.height += 20;
}
}
有了自己的statusBarWindow就万事大吉了嘛?答案肯定不是啊,是我还写什么。
UIStatusBar是不停改变的,一种通过UIApplication.h
的API
- (void)setStatusBarHidden:(BOOL)hidden withAnimation:(UIStatusBarAnimation)animation
- (void)setStatusBarStyle:(UIStatusBarStyle)statusBarStyle animated:(BOOL)animated
@property(readwrite, nonatomic) UIStatusBarStyle statusBarStyle
UIViewController.h
中的相关API也能修改状态栏
另一种是因为来电、导航、录音等导致系统自己修改的。这种修改只会导致状态栏的底色发生改变,字体颜色默认都是白色。
对于前一种修改,我们可以通过hook住相关API来实现,对于运行时的方法hook有太多的教程和案例,这里就不叙述了。那对于系统自己修改的,我们怎么来处理呢?
我们知道UIStatusBar就是系统状态栏的具体UIView,先把它拿到。通过getTopView
可以拿到UIStatusBar
- (UIView *)getTopView {
NSString *result = [NSString stringWithFormat:@"%@%@%@",[self getUtilName1:YES], [self getUtilName2:YES],[self getUtilName3:YES]];
id app = [UIApplication sharedApplication];
id view = objc_msgSend(app, NSSelectorFromString(result));
return view;
}
//下面几个方法获取我们需要的方法名
- (NSString *)getUtilName1:(BOOL)isTop {
if (isTop) {
return @"sta";
} else {
return @"k";
}
}
- (NSString *)getUtilName2:(BOOL)isTop {
if (isTop) {
return @"tus";
} else {
return @"ey";
}
}
- (NSString *)getUtilName3:(BOOL)isTop {
if (isTop) {
return @"Bar";
} else {
return @"Window";
}
}
获取到具体的UIStatusBar对象之后,我们来看看系统到底是怎么实现来电、导航等状态栏改变的。
系统状态栏的底色是通过一个名为UIStatusBarBackgroundView的subview来反应的,文字部分主要通过一个名为UIStatusBarForegroundView的subview来反应。
我们使用Aspect来截获UIStatusBar的- (void)didAddSubview:(UIView *)subview;
。具体代码如下:
[[self getTopView] aspect_hookSelector:@selector(didAddSubview:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo, UIView *subview){
NSArray *subviews = [[aspectInfo instance] subviews];
NSLog(@"didAddSubview, subviews : %@, subview : %@", subviews, subview);
} error:NULL];
结果如下:
第一次断点:
第二次断点:
实践证明,底色的改变居然是通过subview来实现的,每当需要修改底色时都会有新的UIStatusBarBackgroundView和UIStatusBarForegroundView加到UIStatusBar中,同时移掉旧的subview.
好了,知道怎么回事,就可以来监听这个底色改变了。
- (void)initStatusBarData {
//初始化状态栏状态
[self topViewBackgroundColorDidChange];
__weak typeof(self) weakSelf = self;
[[self getTopView] aspect_hookSelector:@selector(willRemoveSubview:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo, UIView *subview) {
NSArray *subviews = [[aspectInfo instance] subviews];
//count的计算是因为启动本app之后再开始录音,则会出现一层UIView,subviews.count会为4
int count = 0;
for(UIView *view in subviews) {
if ([view isKindOfClass:NSClassFromString(@"UIStatusBarForegroundView")] || [view isKindOfClass:NSClassFromString(@"UIStatusBarBackgroundView")]) {
count++;
}
}
if (count == 3) {
[weakSelf topViewBackgroundColorDidChange];
}
} error:NULL];
}
- (void)topViewBackgroundColorDidChange {
UIView *colorView = [self getTopColorView];
UIColor *color = colorView.backgroundColor;
if (color) {
//状态栏此时有颜色
} else {
//使用UINavigationBar的颜色
}
}
- (UIView *)getTopColorView {
return [[[self getTopView] subviews] firstObject];
}
搞定收工!