首先来看一下UIWindow 继承关系
UIView的功能
负责渲染区域的内容,并且响应该区域内发生的触摸事件
UIWindow
在iOS App中,UIWindow是最顶层的界面内容,我们使用UIWindow和UIView来呈现界面。UIWindow并不包含任何默认的内容,但是它被当作UIView的容器,用于放置应用中所有的UIView。
从继承关系来看,UIWindow继承自UIView,所以UIWindow除了具有UIView的所有功能之外,还增加了一些特有的属性和方法,而我们最常用的方法,就是在App刚启动时,调用UIWindow的rootViewController(必须指定根控制器) 和 makeKeyAndVisible方法
状态栏和键盘都是特殊的UIWindow。
UIWindow的主要作用有:
1.作为UIView的最顶层容器,包含应用显示所有的UIView;
2.传递触摸消息和键盘事件给UIView;
UIWindow的层级:
UIWindow的层级由一个UIWindowLevel类型属性windowLevel,该属性指示了UIWindow的层级,windowLevel有三种可取值。
并且层级是可以做加减的self.window.windowLevel = UIWindowLevelAlert+1;
UIKIT_EXTERN const UIWindowLevel UIWindowLevelNormal; UIKIT_EXTERN const UIWindowLevel UIWindowLevelAlert; UIKIT_EXTERN const UIWindowLevel UIWindowLevelStatusBar __TVOS_PROHIBITED;
Normal ,StatusBar,Alert.输出他们三个层级的值,我们发现从左到右依次是0,1000,2000,也就是说Normal级别是最低的,StatusBar处于中级,Alert级别最高。而通常我们的程序的界面都是处于Normal这个级别的,系统顶部的状态栏应该是处于StatusBar级别,提醒用户等操作位于Alert级别。根据window显示级别优先原则,级别高的会显示在最上层,级别低的在下面,我们程序正常显示的view在最底层;
四个关于window变化的通知
UIKIT_EXTERN NSNotificationName const UIWindowDidBecomeVisibleNotification; // nil UIKIT_EXTERN NSNotificationName const UIWindowDidBecomeHiddenNotification; // nil UIKIT_EXTERN NSNotificationName const UIWindowDidBecomeKeyNotification; // nil UIKIT_EXTERN NSNotificationName const UIWindowDidResignKeyNotification; // nil
这四个通知对象中的object都代表当前已显示(隐藏),已变成keyWindow(非keyWindow)的window对象,其中的userInfo则是空的。于是我们可以注册这个四个消息,再打印信息来观察keyWindow的变化以及window的显示,隐藏的变动 . 变成keywindow 的流程是这样的(默认的window -->点击弹出AlertView)
1.程序默认的window先显示出来
2.默认的window再变成keywindow
3.AlertView 的window显示出来
4.默认的window变成keywindow
5.最终AlertView的window变成keywindow
根据测试我们同时可以知道默认的window的level是0,即normal级别;AlertView的window的level是1996,比Alert级别稍微低了一点儿。同时我们可以看出ActionSheet的window的level是2001; 键盘window 的level是最高的在一切之上(我测试的是不管level 设置为多少都在键盘window 的下面)
当我们点击ActionSheet cancel的时候,我们会看到流程
1.首先ActionSheet 的window变成非keyWindow
2.程序默认的window变成keywindow
3.ActionSheet 的window隐藏掉
弹出AlertView和ActionSheet的时候系统会帮你改变keyWindow 但是当弹出键盘的时候keyWindow是不变的!
下面有说keyWindow是用来接收键盘以及非触摸类的消息(文档有误 是指点击事件等 不是keywindow 也是可以接受事件的消息的)
keyWindow
当前app可以打开的多个window 如系统状态栏其实就是一个window ,程序启动的时候创建的默认的window ,弹出键盘也是一个window ,alterView 弹框也是window 。但是keyWindow只有一个 ,一般情况下就是我们程序启动时设置的默认的window
官方文档中是这样解释的 “The key window is the one that is designated to receive keyboard and other non-touch related events. Only one window at a time may be the key window." 翻译过来就是说,keyWindow是指定的用来接收键盘以及非触摸类的消息,而且程序中每一个时刻只能有一个window是keyWindow。
文档有误:app可以打开的多个window 每个里面加入都加入UITextField 和点击事件 发现 都可以处理事件和接受键盘消息
问题一:一个应用程序只能有一个主窗口,如果程序中创建了两个Window,那么谁是主窗口?
①iOS 7 以后,主窗口和次窗口是没有区别的 ②iOS 7 之前,如果后面的窗口设置为主窗口,会把之前设置的主窗口覆盖掉
问题二:只有主窗口才能响应键盘的输入事件?
在ios9.3的模拟器中,主窗口和非主窗口中的输入框都能输入文字,但是在ios6.1的模拟器中, 非主窗口的输入框不能输入文字。
获取keyWindow的方式
UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; UIViewController *rootViewController = keyWindow.rootViewController;
注意:keyWindow不是一成不变的,当你创建alertView或者ActionSheet的时候,它们所在的window会变成keyWindow。也就是说系统默认创建的window首先变成keywindow,而当弹框的时候,alertView所在的window变成keywindow,默认的keywindow变成非keywindow。
@property(nonatomic,readonly) NSArray *windows;
在windows数组里面,window是根据windowLevel来排列的,最后一个覆盖在最上面。这里的windows数组不包括系统提供的window,比如说状态栏就是在一个系统创建的window里面。
测试代码如下
#import "AppDelegate.h" @interface AppDelegate () @property(strong, nonatomic) UIWindow *normalWindow; @property(strong, nonatomic) UIWindow *coverStatusBarWindow; @property(strong, nonatomic) UIWindow *alertLevelWindow; @end @implementation AppDelegate - (void)coverWindowOnClicked{ NSLog(@"tap tap 11111"); [[NSNotificationCenter defaultCenter]postNotificationName:@"kOnClickedStatusBarNotification" object:self userInfo:nil]; } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ NSLog(@"touchesBegan touchesBegan55555555555"); } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //1. self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.backgroundColor = [UIColor yellowColor]; self.window.rootViewController = [[UIViewController alloc]init]; [self.window makeKeyAndVisible]; NSLog(@"1hahah%f",[UIApplication sharedApplication].keyWindow.windowLevel); //2. UIWindow *normalWindow = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; normalWindow.backgroundColor = [UIColor grayColor]; normalWindow.windowLevel = UIWindowLevelNormal; normalWindow.rootViewController = [[UIViewController alloc]init]; [normalWindow makeKeyAndVisible]; self.normalWindow = normalWindow; UITextField *tf = [[UITextField alloc] init]; tf.frame = CGRectMake(10, 64, 100, 20); tf.borderStyle = UITextBorderStyleRoundedRect; [self.normalWindow addSubview:tf]; UITapGestureRecognizer *tap1 = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(coverWindowOnClicked)]; [self.normalWindow addGestureRecognizer:tap1]; NSLog(@"2hahah%f",[UIApplication sharedApplication].keyWindow.windowLevel); //2. 创建覆盖着状态栏的window UIWindow * coverStatusBarWindow =[[UIWindow alloc]initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 20)]; coverStatusBarWindow.rootViewController = [[UIViewController alloc]init]; coverStatusBarWindow.backgroundColor = [UIColor redColor]; //级别要比 状态栏的级别高 coverStatusBarWindow.windowLevel = UIWindowLevelStatusBar+1; [coverStatusBarWindow makeKeyAndVisible]; self.coverStatusBarWindow = coverStatusBarWindow; UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(coverWindowOnClicked)]; [self.coverStatusBarWindow addGestureRecognizer:tap]; //想移除coverStatusBarWindow 将其赋值为空 // self.coverStatusBarWindow = nil; // 3.创建UIwindow1 self.alertLevelWindow = [[UIWindow alloc] initWithFrame:CGRectMake(50, 150, 200, 250)]; self.alertLevelWindow.backgroundColor = [UIColor blueColor]; UIViewController *vc1 = [[UIViewController alloc] init]; self.alertLevelWindow.rootViewController = vc1; self.alertLevelWindow.windowLevel = UIWindowLevelAlert; [self.alertLevelWindow makeKeyAndVisible]; // 给UIwindow1添加一个输入框 UITextField *tf1 = [[UITextField alloc] init]; tf1.frame = CGRectMake(10, 64, 100, 20); tf1.borderStyle = UITextBorderStyleRoundedRect; [self.alertLevelWindow addSubview:tf1]; UITapGestureRecognizer *tap2 = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(coverWindowOnClicked)]; [self.alertLevelWindow addGestureRecognizer:tap2]; NSLog(@"1hahah%f",[UIApplication sharedApplication].keyWindow.windowLevel); NSLog(@"windows 刚启动 ---%@",[UIApplication sharedApplication].windows); [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(keyBoardShow:) name:UIKeyboardDidShowNotification object:nil]; return YES; } - (void)keyBoardShow:(NSNotification *)notif{ NSLog(@"windows 键盘弹出 ---%@",[UIApplication sharedApplication].windows); NSLog(@"2hahah%f",[UIApplication sharedApplication].keyWindow.windowLevel); }
运行效果如下
可以得出以下结论: (实践是检验真理的唯一标准 网上有很多是错误的还是自己实践最好)
1) 同一层级的 最后一个显示出来,上一个被覆盖
2)UIWindow在显示的时候是不管KeyWindow是谁,都是Level优先的,即Level最高的始终显示在最前面。
3)谁最后设置的 makeKeyAndVisible 谁就是keyWindow 其他的也会显示出来 所有的window都可以监听键盘 和点击的事件
看打印
windows 刚启动 ---( "<UIWindow: 0x7ff636e01b80; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x60000005c6e0>; layer = <UIWindowLayer: 0x600000027640>>", "<UIWindow: 0x7ff636d07580; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x60000005fa40>; layer = <UIWindowLayer: 0x6000000276a0>>", "<UIWindow: 0x7ff636d0bdd0; frame = (0 0; 320 20); gestureRecognizers = <NSArray: 0x6080002418c0>; layer = <UIWindowLayer: 0x600000037bc0>>", "<UIWindow: 0x7ff636d0d500; frame = (50 150; 200 250); gestureRecognizers = <NSArray: 0x600000241ec0>; layer = <UIWindowLayer: 0x600000037e80>>" ) windows 键盘弹出 ---( "<UIWindow: 0x7ff636e01b80; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x60000005c6e0>; layer = <UIWindowLayer: 0x600000027640>>", "<UIWindow: 0x7ff636d07580; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x60000005fa40>; layer = <UIWindowLayer: 0x6000000276a0>>", "<UIWindow: 0x7ff636d0bdd0; frame = (0 0; 320 20); gestureRecognizers = <NSArray: 0x6080002418c0>; layer = <UIWindowLayer: 0x600000037bc0>>", "<UIWindow: 0x7ff636d0d500; frame = (50 150; 200 250); gestureRecognizers = <NSArray: 0x600000241ec0>; layer = <UIWindowLayer: 0x600000037e80>>", "<UITextEffectsWindow: 0x7ff636c110d0; frame = (0 0; 320 568); opaque = NO; autoresize = W+H; layer = <UIWindowLayer: 0x60800003ae80>>", "<UIRemoteKeyboardWindow: 0x7ff636f06f90; frame = (0 0; 320 568); opaque = NO; autoresize = W+H; layer = <UIWindowLayer: 0x60800003f860>>" )
发现
(1)UITextEffectsWindow
这是iOS8引入的一个新window,是键盘所在的window。它的windowLevel是最高的。
(2)UIRemoteKeyboardWindow
iOS9之后,新增了一个类型为 UIRemoteKeyboardWindow 的窗口用来显示键盘按钮。
如何销毁一个UIWindow
self.testWindow.hidden = YES; self.testWindow = nil;
window应用场景
在应用开发中,将某些界面覆盖在所有界面的最上层。这个时候,我们就可以手工创建一个新的UIWindow。需要注意的是,和创建UIView不同,UIWindow一旦被创建(并设置rootViewController 和 makeKeyAndVisible),它就自动地被添加到整个界面上了(当然,其windowLevel要足够高)
场景一:
支付宝钱包等App的密码保护页面是基于UIWindow实现的,当用户从应用的任何界面按Home键退出,过一段时间再从后台切换回来时,显示一个密码输入界面。只有用户输入了正确的密码,才能进入退出前的界面。因为这个密码输入界面可能从任何应用界面弹出,并且需要盖住所有界面的最上层,所以很合适做一个UIWindow来实现
场景二:
自定义statusBar 解决 点击statusBar滑动到顶部
http://www.cnblogs.com/junhuawang/p/6003191.html
场景三:
一个手势解锁 0.设置手势界面 1.app进入后台跳转前台是进入手势解锁界面 2.点击某个按钮进入手势界面