Source Code Learning - JDStatusBarNotification

RunLoop 和 NSTimer

每次显示时先要将之前设置过的 timer 取消

// cancel previous dismissing & remove animations
[[NSRunLoop currentRunLoop] cancelPerformSelector:@selector(dismiss) target:self argument:nil];

timer 的设置代码如下:

- (void)setDismissTimerWithInterval:(NSTimeInterval)interval; {
    [self.dismissTimer invalidate];             // 重新赋值时先要使其无效
    self.dismissTimer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:interval]
                                             interval:0 target:self selector:@selector(dismiss:) userInfo:nil repeats:NO];
    [[NSRunLoop currentRunLoop] addTimer:self.dismissTimer forMode:NSRunLoopCommonModes];
}

为了能在用户拖拽页面的时候 timer 正确计时,将 timer 添加到 NSRunLoopCommonModes 模式下

UIWindowLevel

每个应用都有一个主 window,通常在 AppDelegate 中设置

self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:
                                  [[SBExampleViewController alloc] initWithStyle:UITableViewStyleGrouped]];

[self.window makeKeyAndVisible];

makeKeyAndVisible 方法是将某个 window 设置为主窗口并设置为可见,这个窗口的级别使用默认值 UIWindowLevelNormal,UIWindowLevel 的级别如下

UIKIT_EXTERN const UIWindowLevel UIWindowLevelNormal;
UIKIT_EXTERN const UIWindowLevel UIWindowLevelAlert;
UIKIT_EXTERN const UIWindowLevel UIWindowLevelStatusBar __TVOS_PROHIBITED;

其级别顺序是 Normal < StatusBar < Alert,也就是说 Alert 级别的 window 会在最前端显示。

其中 UIKIT_EXTERN 的定义如下

#ifdef __cplusplus
#define UIKIT_EXTERN        extern "C" __attribute__((visibility ("default")))
#else
#define UIKIT_EXTERN            extern __attribute__((visibility ("default")))
#endif

这几个变量的定义在该文件中可见

extern “C” 是连接申明(linkage declaration),被 extern “C” 修饰的变量和函数是按照 C 语言方式编译和连接的

所以我们如果用 AppDelegate 中创建的这个最低级别作为显示状态栏的窗口的话,很可能会被遮盖,所以首先要创建一个状态栏级别的窗口,创建窗口的代码见 - (UIWindow *)overlayWindow; 方法。

self.overlayWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.overlayWindow.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.overlayWindow.backgroundColor = [UIColor redColor];
self.overlayWindow.userInteractionEnabled = NO;
self.overlayWindow.windowLevel = UIWindowLevelStatusBar;
self.overlayWindow.rootViewController = [[UIViewController alloc] init];
self.overlayWindow.rootViewController.view.backgroundColor = [UIColor clearColor];
self.overlayWindow.hidden = NO;

需要注意的是,窗口创建后默认是不显示的,设置 hidden 为 NO 即可显示,如上我们创建了一个红色的窗口,它可以和 AppDelegate 中设置的主窗口并存,由于它的级别高,所以完全覆盖了主窗口显示红色。

屏幕旋转

当屏幕旋转时,我们创建的 overlayWindow 也要处理旋转,它的旋转 transform 应当和主窗口相同,mainApplicationWindowIgnoringWindow 方法用来获取主窗口,之后将 overlayWindow 的 transform 和 frame 设置为主窗口一致即可,overlayWindow 中的 topBar 的 frame 也需要重新设置。

参考文章

UIWindow的一点儿思考
状态栏提示控件的实现原理

请我喝汽水儿