UIWindow 详解及使用场景

Posted 俊华的博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UIWindow 详解及使用场景相关的知识,希望对你有一定的参考价值。

首先来看一下UIWindow 继承关系

方法和属性

NS_CLASS_AVAILABLE_ios(2_0) @interface UIWindow : UIView
//window的屏幕,默认是 [UIScreen mainScreen] ,不能更改,否则没有界面
@property(nonatomic,strong) UIScreen *screen NS_AVAILABLE_IOS(3_2);  
//window的视图层级,默认是0.0
@property(nonatomic) UIWindowLevel windowLevel;           
//是否是keyWindow
@property(nonatomic,readonly,getter=isKeyWindow) BOOL keyWindow;
//该方法不应该被手动调用,当window变为keyWindow时会被自动调用来通知window。可以继承UIWindow重写此方法来实现功能
- (void)becomeKeyWindow;   
// 该方法不应该被手动调用,当window不再是keyWindow时(例如其他window实例调用了- makeKeyWindow或- makeKeyAndVisible方法)会被自动调用来通知window。可以继承UIWindow重写此方法来实现功能。                          
- (void)resignKeyWindow;                              
//该方法使window变为keyWindow,但不影响显示状态
- (void)makeKeyWindow;

//显示主window并将window设为key并显示,该方法让window显示并变为keyWindow。调用该方法会让window排在其level组中的最上面。如果只想改变window的显示而不影响keyWindow状态,可以直接设置window的hidden属性为NO。
- (void)makeKeyAndVisible;                            
//根控制器
@property(nullable, nonatomic,strong) UIViewController *rootViewController NS_AVAILABLE_IOS(4_0); 
//UIApplication调用window的该方法给window分发事件,window再将事件分发到合适的目标,比如将触摸事件分发到真正触摸的view上。可以直接调用该方法分发自定义事件。
- (void)sendEvent:(UIEvent *)event;                   
// 把该window中的一个坐标转换成在目标window中时的坐标值
- (CGPoint)convertPoint:(CGPoint)point toWindow:(nullable UIWindow *)window;   
// 把目标window中的一个坐标转换成在该window中时的坐标值
- (CGPoint)convertPoint:(CGPoint)point fromWindow:(nullable UIWindow *)window;  
// 把该window中的一个矩阵转换成在目标window中时的矩阵值
- (CGRect)convertRect:(CGRect)rect toWindow:(nullable UIWindow *)window;
// 把目标window中的一个矩阵转换成在该window中时的矩阵值
- (CGRect)convertRect:(CGRect)rect fromWindow:(nullable UIWindow *)window;

 

 

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; //默认,值为0
UIKIT_EXTERN const UIWindowLevel UIWindowLevelAlert; //值为2000 
UIKIT_EXTERN const UIWindowLevel UIWindowLevelStatusBar ; // 值为1000

 

 

 

Normal ,StatusBar,Alert.输出他们三个层级的值,我们发现从左到右依次是0,1000,2000,也就是说Normal级别是最低的,StatusBar处于中级,Alert级别最高。而通常我们的程序的界面都是处于Normal这个级别的,系统顶部的状态栏应该是处于StatusBar级别,提醒用户等操作位于Alert级别。根据window显示级别优先原则,级别高的会显示在最上层,级别低的在下面,我们程序正常显示的view在最底层;

 

四个关于window变化的通知

 

UIKIT_EXTERN NSString *const UIWindowDidBecomeVisibleNotification; // 当window激活时并展示在界面的时候触发,返回空
UIKIT_EXTERN NSString *const UIWindowDidBecomeHiddenNotification;  // 当window隐藏的时候触发,暂时没有实际测,返回空
UIKIT_EXTERN NSString *const UIWindowDidBecomeKeyNotification;     // 当window被设置为keyWindow时触发,返回空
UIKIT_EXTERN NSString *const UIWindowDidResignKeyNotification;     // 当window的key位置被取代时触发,返回空

 

 程序启动然后点击弹框出来 keywindow的变化

这四个通知对象中的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.点击某个按钮进入手势界面

 

以上是关于UIWindow 详解及使用场景的主要内容,如果未能解决你的问题,请参考以下文章

Java线程池详解

为啥我看不到我的新 UIWindow?

架构师内功心法,只是单纯听说过的原型模式详解

架构师内功心法,只是单纯听说过的原型模式详解

Flask 编写http接口api及接口自动化测试

mongodb位运算$bit介绍及使用场景详解