Hello, World!

UIWindow

字数统计: 1.4k阅读时长: 5 min
2017/10/11 Share

UIWindow是最顶层的界面容器,本文将讲解它的特点及一些使用技巧。

UIWindow简介

在iOS应用中,我们使用UIWindow和UIView来呈现界面。UIWindow并不包含任何默认的内容,但是它被当作UIView的容器,用于放置应用中所有的UIView。而每一个UIView通常都是用来表示具体的某一部分界面,例如一段文字、一张图片等,当然,你也可以用UIView来当作其他UIView的容器。所以UIWindow更多的时候只作为UIView的顶层容器存在。从继承关系上,我们能看到UIWindow继承自UIView,所以UIWindow除了具UIView的所有功能外,还增加了一些特有的属性和方法。

1
API_AVAILABLE(ios(2.0)) @interface UIWindow : UIView

而我们最常用的方法,就是在程序刚启动时,调用UIWindow的makeKeyAndVisible方法,使整个程序界面可见,代码如下所示:

1
2
3
4
5
6
7
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[[ViewController alloc] init]];
[self.window makeKeyAndVisible];
return YES;
}

所以UIWindow的主要作用有:
1.作为UIView的最顶层容器,包含应用显示所需要的所有的UIView。
2.传递触摸消息和键盘事件给UIView。

为UIWindow增加UIView

通常有两种办法给UIWindow增加子UIView:
1.通过调用addSubView方法。因为UIWindow是UIView的子类,所以它可以使用UIView的addSubView方法给自己增加子UIView,从而承担容器的作用。
2.通过设置其特有的rootViewController属性。通过设置该属性为要添加view对应的UlViewController, UIWindow将会自动将其view添加到当前window中,同时负责维护ViewController 和 view的生命周期。我们在之前的 application: didFinishLaunching- WithOptions:示例代码中看到的就是这种办法。

系统对UIWindow的使用

通常在一个程序中只会有一个UIWindow,但有些时候我们调用系统的控件(例如UIAlertView)时,iOS系统为了保证UIAlertView在所有的界面之上,它会临时创建一个新的UIWindow,通过将其UIWindow的UIWindowLevel设置得更高,让UIAlertView盖在所有的应用界面之上。
除了在应用中通过调用API弹出系统的各种控件外,有些时候iOS系统也会根据当时的各种情况(例如电池电量不足,收到来电或短信等)通过创建新的UlWindow来作为系统控件的容器。

WindowLevel

那么是不是新创建的UIWindow一定会覆盖在界面的最上面呢?其实并不是这样。UIWindow有一个UIWindowLevel的属性,该属性定义了UIWindow的层级,系统定义的WindowLevel一共有3种取值,如下所示:

1
2
3
UIKIT_EXTERN const UIWindowLevel UIWindowLevelNormal;
UIKIT_EXTERN const UIWindowLevel UIWindowLevelAlert;
UIKIT_EXTERN const UIWindowLevel UIWindowLevelStatusBar;

我们通过如下代码将这些值输出:

1
NSLog(@"UIWindowLevelNormal=%f UIWindowLevelStatusBar=%f UIWindowLevelAlert=%f", UIWindowLevelNormal, UIWindowLevelStatusBar, UIWindowLevelAlert);

最终得到的结果是:
UIWindowLevelNormal =0.000000
UIWindowLevelStatusBar =1000.000000
UIWindowLevelAlert =2000.000000

从中我们就能够看出来了,默认程序的UIWindow的层级是UIWindowLevelNormal,当系统需要在其上面覆盖UIAlertView时,就会创建一个层级是UIWindowLevelAlert的UIWindow,因为其WindowLevel值更高,所以就覆盖在上面了。

在实际应用中,WindowLevel的取值并不限于上面提到的3个值。例如在上面的例子中,我们可以在UIAlertView的点击回调中,査看该view对应的UIWindow的WindowLevel值,其WindowLevel值为1996。

手工创建UIWindow

有些时候,我们也希望在应用开发中,将某些界面覆盖在所有界面的最上层。这个时候我们就可以手工创建一个新的UIWindow。需要注意的是,和创建UIView不同,UIWindow—旦被创建,它就自动被添加到整个界面上了,且hidden属性默认值为YES。下面是一个示例代码,在代码中,我们创建一个新的UIWindow,并且把它添加到界面中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@implementation ViewController {
UIWindow *_window;
}

- (void)exchangeKeyWindow {

_window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; // 一旦创建好了之后,自动添加在整个界面上
_window.windowLevel = UIWindowLevelNormal;
_window.hidden = NO;
_window.backgroundColor = [UIColor redColor];

UIGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(hideWindow:)];
[_window addGestureRecognizer:gesture];
}

- (void)hideWindow:(UIGestureRecognizer *)ges {
_window.hidden = YES;
_window = nil;
}

@end

通过以上代码,我们就简单实现了手工创建UIWindow并将其显示的需求。除了直接构造UIWindow实例外,对于一些复杂的需求,我们也可以将UIWindow继承,然后将相关逻辑都封装在UIWindow子类中。

最后还有一点需要注意的是,如果我们创建的UIWindow需要处理键盘事件,那就需要合理地将其设置为keyWindow。keyWindow是被系统设计用来接收键盘和其他非触摸事件的UIWindow。我们可以通makeKeyWindow和resignKeyWindow方法来将自己创建的 UIWindow 实例设置成keyWindow。
支付宝客户端的手势解锁功能,也是UIWindow的一个极好的应用。除了密码输入界面外, 其他适合用UIWindow来实现的功能还包括:应用的启动介绍页、应用内的通知提醒显示、应用内的弹框广告等。

不要滥用UIWindow

通过创建UIWindow,我们很容易地实现了将某个特定界面置于最上层的效果,但是这种特性不应该被滥用。很多时候,如果弹出界面明显属于某一个ViewController,那么更适合把弹出的界面当作这个ViewController的view的subView来实现。
常见的滥用方式是把需要的弹出界面都设置成单例,需要的时候就调用显示。这种做法会使得新创建的UIWindow一直得不到释放。并且当出现多个UIWindow需要相互有层级覆盖关系时,实现起来会比较复杂。

参考资料:《iOS开发进阶》

CATALOG
  1. 1. UIWindow简介
  2. 2. 为UIWindow增加UIView
  3. 3. 系统对UIWindow的使用
    1. 3.1. WindowLevel
    2. 3.2. 手工创建UIWindow
    3. 3.3. 不要滥用UIWindow