天天看点

UIWindow的研究

一、UIWindow是一种特殊的UIView,通常在一个程序中只会有一个UIWindow,但可以手动创建多个UIWindow,同时加到程序里面。UIWindow在程序中主要起到三个作用:

  1、作为容器,包含app所要显示的所有视图

  2、传递触摸消息到程序中view和其他对象

  3、与UIViewController协同工作,方便完成设备方向旋转的支持

二、通常我们可以采取两种方法将view添加到UIWindow中:

  1、addSubview

  直接将view通过addSubview方式添加到window中,程序负责维护view的生命周期以及刷新,但是并不会为去理会view对应的ViewController,因此采用这种方法将view添加到window以后,我们还要保持view对应的ViewController的有效性,不能过早释放。

  2、rootViewController

  rootViewController时UIWindow的一个遍历方法,通过设置该属性为要添加view对应的ViewController,UIWindow将会自动将其view添加到当前window中,同时负责ViewController和view的生命周期的维护,防止其过早释放

三、WindowLevel的描述,每个UIWindow都包含WindowLevel 属性

  UIWindow在显示的时候会根据UIWindowLevel进行排序的,即Level高的将排在所有Level比他低的层级的前面。下面我们来看UIWindowLevel的定义:

    const UIWindowLevel UIWindowLevelNormal;

    const UIWindowLevel UIWindowLevelAlert;

    const UIWindowLevel UIWindowLevelStatusBar;

    typedef CGFloat UIWindowLevel;

  IOS系统中定义了三个window层级,其中每一个层级又可以分好多子层级(从UIWindow的头文件中可以看到成员变量CGFloat _windowSublevel;),不过系统并没有把则个属性开出来。UIWindow的默认级别是UIWindowLevelNormal,我们打印输出这三个level的值分别如下:

2012-03-27 22:46:08.752 UIViewSample[395:f803] Normal window level: 0.000000  

2012-03-27 22:46:08.754 UIViewSample[395:f803] Normal window level: 2000.000000  

2012-03-27 22:46:08.755 UIViewSample[395:f803] Normal window level: 1000.000000  

四、创建window的方式:

(1)使用storyboard:如果程序员为app创建了一个storyboard,并在info.plist中指定它为main storyboard,那么在app启动的时候,iOS会自动帮程序员做这样几件事情:

  1. 实例化一个window;
  2. 加载main storyboard,并且实例化其中的root view controller;
  3. 将这个view controller赋值给window.rootViewController,并显示这个window。

(2)使用nib文件:如果不使用storyboard,也可以用nib文件来代替。将一个window对象拖拽到Interface Builder文件中,并将这个文件指定为app的main interface。那么在app启动的时候,iOS也会自动创建window对象。为了确保window的大小与屏幕大小吻合,需要在Interface Builder中对window对象勾选Full Screen at Launch这个属性。

(3)手写代码:当然也可以通过手写代码的方式创建window。比如官方示例代码:

- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

    myViewController = [[MyViewController alloc] init];

    window.rootViewController = myViewController;

    [window makeKeyAndVisible];

    return YES;

}

需要注意的是,window的尺寸永远应该是屏幕的尺寸,不应该考虑状态栏等元素,因为这些是view controller应该处理的问题。

五、Action Sheet和Alert View

知道了window的存在之后,感觉也能知道很多事情。

比如,iOS中的UIActionSheet和UIAlertView其实是显示在另一个window上的。

监听UIWindowDidResignKeyNotification,可以发现,当action sheet弹出时,UIWindowDidResignKeyNotification通知被发送了。此时检查app所在的window,发现它已经不再是key window了。

六:使用(悬浮窗,QQ手势密码,启动图片)

悬浮窗的使用

以前我总以为所有的view都是被拘束在UIViewController的view中的,所以一直不知道悬浮窗的效果应该如何实现。但实际上,UIWindow本身就是一个UIView,可以直接在UIWindow上添加子视图,做出悬浮的效果。(虽然这样不符合苹果的设计规范)

[[[UIApplication sharedApplication].delegate window] addSubview:suspendView];

呈现出视图悬浮在app之上的效果

好奇尝试了一些奇怪的情景O.O

如果window没有占满整个屏幕会怎样呢?

默认情况下,window中的视图依然能照常显示,但是触屏事件无法正常分发。

官方文档中这样描述:

Because a window doesn’t receive touch events outside of its bounds and views aren’t clipped to the window’s bounds by default, an improperly sized window might not be able to deliver touch events to all its views.

用手写代码的方式可以创建一个任意大小的window,比如在application:willFinishLaunchingWithOptions:方法中写:

self.window = [[XSQWindow alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];

运行程序时,window中的子视图依然可以照常显示,包括window外的部分,但是在点击window之外的区域时,终端会输出错误信息:

unexpected nil window in _UIApplicationHandleEventFromQueueEvent, _windowServerHitTestWindow: <XSQWindow: 0x14c614a20; baseClass = UIWindow; frame = (0 0; 200 200); gestureRecognizers = <NSArray: 0x174059740>; layer = <UIWindowLayer: 0x174220e60>>

并且触屏点对应的视图无法接收到这次的触屏事件。

如果在一个app中创建多个window会怎么样?

也是可以做到在一个app中创建多个window的,而且似乎也不会怎么样。

触屏事件会根据触摸点的位置,被UIApplication分发到对应的window中。