UIWindow 详解及使用场景

首先来看一下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.点击某个按钮进入手势界面

时间: 2024-07-29 00:29:21

UIWindow 详解及使用场景的相关文章

深入MySQL用户自定义变量:使用详解及其使用场景案例

一.前言 在前段工作中,曾几次收到超级话题积分漏记的用户反馈.通过源码的阅读分析后,发现问题出在高并发分布式场景下的计数器上.计数器的值会影响用户当前行为所获得积分的大小.比如,当用户在某超级话题下连续第n(n即计数器的值)次进行转发帖子时,将会获得与n相关的分数.然而,在第一次改进后问题依然存在.所以,这次在之前的基础上,通过使用MySQL变量的途径来解决该问题. 二.到底MySQL的变量分哪几类? MySQL变量一共分为两大类:用户自定义变量和系统变量.如下: 用户自定义变量 局部变量 会话

详解 Redis 应用场景及应用实例

Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API.从2010年3月15日起,Redis的开发工作由VMware主持. 1. MySql+Memcached架构的问题 实际MySQL是适合进行海量数据存储的,通过Memcached将热点数据加载到cache,加速访问,很多公司都曾经使用过这样的架构,但随着业务数据量的不断增加,和访问量的持续增长,我们遇到了很多问题: 1.MySQL需要不断进行拆库拆表,Memc

详解 Redis 应用场景及原理

本文转自https://blog.csdn.net/niucsd/article/details/50966733,描述了redis实现原理和应用场景,篇幅较长,有意学习redis的同学可耐心阅读. Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API.从2010年3月15日起,Redis的开发工作由VMware主持. 1. MySql+Memcached架构的问题 实际MySQL是适合进行海量数据存储的,通过M

redis命令详解与使用场景举例——String

APPEND key value 如果 key 已经存在并且是一个字符串, APPEND 命令将 value 追加到 key 原来的值的末尾. 如果 key 不存在, APPEND 就简单地将给定 key 设为 value ,就像执行 SET key value 一样. 可用版本: 2.0.0+ 时间复杂度: 平摊O(1) 返回值: 追加 value 之后, key 中字符串的长度. 对不存在的 key 执行 APPEND redis> EXISTS myphone # 确保 myphone 不

一对一关联查询注解@OneToOne的实例详解

表的关联查询比较复杂,应用的场景很多,本文根据自己的经验解释@OneToOne注解中的属性在项目中的应用.本打算一篇博客把增删改查写在一起,但是在改的时候遇到了一些问题,感觉挺有意思,所以写下第二篇专门讲修改. 一.单向@OneToOne实例详解 假设一个场景,一个人只能领养一只宠物,根据人能够找到宠物,并且查看宠物的信息,关系是单向的. 创建人与宠物的数据表结构.下载地址:Person,Pet数据库建表. 创建实体. Person.java package com.my.model; impo

Ansible系列命令用法详解与使用

Ansible系列命令用法与使用 在上一个文章中已经完成了Ansible的安装,这片文章主要的用来记录Ansible一些命令的用法详解及其使用场景.好了非话不多说,'上菜吧'. Ansible命令行执行方式有Ad-hoc.Ansible-playbook两种方式.Web化执行方式其官方提供了付费产品Tower(10台以内免费),个人的话可以基于API开发类似的Web化产品.此篇文章主要针对于Ad-hoc.Ansible-playbook两种方式做详细介绍. 什么是Ad-hoc.Ansible-p

转:修改ETM,用Ogre实现《天龙八部》地形与部分场景详解

本文主要讲的是<天龙八部>游戏的地形和一部分场景的具体实现,使用C++, Ogre1.6,我摸索了段时间,可能方法用的并不是最好的,但好歹实现了.文章可能讲得有点罗嗦,很多简单的东西都讲了.我是修改了ETM(Editable Terrain Manager)实现的地形,其实单单实现天龙八部的地形场景等的载入根本不需要使用ETM,直接用Ogre的顶点->索引->纹理就可以搞定地形,但我要做的是可以实时编辑的,所以用了ETM,场景其由于很重要的粒子和model等部分我还没去看,所以等以

RabbitMQ,Apache的ActiveMQ,阿里RocketMQ,Kafka,ZeroMQ,MetaMQ,Redis也可实现消息队列,RabbitMQ的应用场景以及基本原理介绍,RabbitMQ基础知识详解,RabbitMQ布曙

消息队列及常见消息队列介绍 2017-10-10 09:35操作系统/客户端/人脸识别 一.消息队列(MQ)概述 消息队列(Message Queue),是分布式系统中重要的组件,其通用的使用场景可以简单地描述为: 当不需要立即获得结果,但是并发量又需要进行控制的时候,差不多就是需要使用消息队列的时候. 消息队列主要解决了应用耦合.异步处理.流量削锋等问题. 当前使用较多的消息队列有RabbitMQ.RocketMQ.ActiveMQ.Kafka.ZeroMQ.MetaMq等,而部分数据库如Re

“全栈2019”Java第一百一十三章:什么是回调?回调应用场景详解

难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第一百一十三章:什么是回调?回调应用场景详解 下一章 "全栈2019"Java异常第一章:什么是异常? 学习小组 加入同步学习小组,共同交流与进步. 方式一:关注头条号Gorhaf,私信"Java学习小组". 方式二:关注公众号Gorhaf,回复"Java学习小