iOS UIWindow 详解

查看滴滴开源的 DoraemonKit 以及阿里开源的youku-sdk-tool-woodpecker
时, 看到启动入口均采用 UIWindow 来实现, 效果如下图悬浮的绿色按钮.

悬浮按钮

拖动悬浮按钮可以随手指一动(图上为模拟器效果), 点击悬浮按钮可以切换页面.

对这种效果的实现方式, 我的第一直觉是采用在 VC 的 touch 事件中进行处理, 但是按钮还需要有点击效果, 这个就难以处理了. 带着疑问查看源码, 发现两个项目中的悬浮框均是采用 UIWindow 的方式来实现, 十分巧妙. 自己关于Window 的使用方式做了一些尝试, 遂有此文.

1 新建的 UIWindow 如何显示?

开始时, 我首先把创建的 UIWindow 添加到 keyWindow 上, 调用 hidden=NO 方法显示正常(注意window实例要被对象持有,避免创建后就销毁), 但是在使用 Xcode 中的渲染工具debug view hierarchy查看 App 的渲染层级时, 直接崩溃. 反复查找, 移除添加到 keyWindow 的代码, 显示和渲染均正常.

UIWindow 属于一个完整渲染层的容器控件, 不能再添加其他 UIWindow 实例. 一个 App 应用属于一个最底层容器, 可以同时承载不同的 UIWindow. 多个 UIWindowApp 中的显示顺序, 按照 windowLevel 的大小, 从前到后进行显示. App 中多个 UIWindow 的响应, 和 View 中多个 子View 的响应方式一样, hitTest 方法返回哪个UIView, 哪个对象就响应.

iOS13 中使用多场景 SceneDelegate 时, 在 - (void)scene: willConnectToSession: options: 方法中创建 Window 时, 要采用 initWithWindowScene: 的方式, 否则 Window将无法显示. 可以采用修改 Xcode 模板的方式, 在新建工程时默认去除 SceneDelegate, 操作方式可参考:MyTemplate.

2 keyWindow 和 delegate 的 Window

一般在 application:didFinishLaunchingWithOptions: 方法中新建 Window, 作为 AppDelegate 的属性. 然后调用 makeKeyAndVisible 方法显示在 App 中. 此时通过[UIApplication sharedApplication].keyWindow 获取的为此 Window. 新建 UIWindow 实例, 调用makeKeyAndVisible 后, 可以改变项目中的 keyWindow. 比如系统的 UIAlertView/UIAlertController/KeyBoard 显示时, 会自动切换 UIApplicationkeyWindow. 将某些控件显示在 keyWindow 时, 需要注意变化, 避免控件意料之外的消失.

UIWindowhidden 属性设置为 false 时, 即可在 APP 显示, 为何还要置位 KeyWindow 呢? 看一下文档上 makeKeyAndVisible 的介绍:


Use this method to make the window key without changing its visibility. 
The key window receives keyboard and other non-touch related events. 
This method causes the previous key window to resign the key status.

主要在第二点, 设置为 key window 后, 可以响应键盘和非触摸事件. App 中的事件一共包含 7 种:


typedef NS_ENUM(NSInteger, UIEventType) {
 UIEventTypeTouches,
 UIEventTypeMotion,
 UIEventTypeRemoteControl,
 UIEventTypePresses API_AVAILABLE(ios(9.0)),
 UIEventTypeScroll      API_AVAILABLE(ios(13.4), tvos(13.4)) API_UNAVAILABLE(watchos) = 10,
 UIEventTypeHover       API_AVAILABLE(ios(13.4), tvos(13.4)) API_UNAVAILABLE(watchos) = 11,
 UIEventTypeTransform   API_AVAILABLE(ios(13.4), tvos(13.4)) API_UNAVAILABLE(watchos) = 14,

后面 3 类为 iOS13 新增, 文档上也没有介绍. 日常开发中接触到的主要是前面 4 类. 第一类为 touch 事件, 是否为 keyWindow 均可以响应; 第二类为加速计事件, 主要指shake; 第三类为远程控制, 比如airpod 对音量的控制; 第四类为按压事件, 文档上解释为a physical button, iPhone 上符合条件的只有音量键和电源键.

UIWindow 在显示/隐藏、keyWindow 切换时,会发送以下 4 种通知:


UIWindowDidBecomeVisibleNotification
UIWindowDidBecomeHiddenNotification
UIWindowDidBecomeKeyNotification
UIWindowDidResignKeyNotification

监听以上通知,测试键盘和2/3/4 类事件发生时,多 UIWindow 的响应情况。粘贴代码过于繁琐,直接说下测试结论:

  1. motion中的shake事件中,keyWindow 才可以响应;
  2. 监听音量键通知AVSystemController_SystemVolumeDidChangeNotification前,需要调用 [[UIApplication sharedApplication] beginReceivingRemoteControlEvents] 方法,非 keyWindow 也可以正常监听, 这和文档介绍的不同;
  3. keyWindow 中的 UITextFieldUITextView 控件响应时, 会自动将所在的 Window 置位 keyWindow.

makeKeyWindowmakeKeyAndVisible 不同,看下 makeKeyWindow 的介绍:


This is a convenience method to show the current window and position it in front of all other windows at the same level or lower. 
If you only want to show the window, change its hidden property to NO.

makeKeyWindow 会把当前 Window 放在其他同级别/低级别的 Window 前面,但是对事件响应没有丝毫影响。当keyWindow 被隐藏后,系统会自动把级别最高的 Window 置为 keyWindow.

3 多 Window 的使用

开头的例子,将悬浮框看做 UIWindow 的实例,可以响应 触摸事件。添加 panGesture手势,在响应方法中,更新 UIWindow 的位置。同时在 UIWindow 添加 UIButton 控件,进而在点击方法中进行页面操作。自己简单实现了一下 FloatingView

使用 UIWindow 可以仿照系统 UIAlertView 的实现方式,随心所欲的 show 到最顶层。将 VC 上添加一个 UIWindow 属性(创建时默认为屏幕大小),自身作为UIWindow的根控制器,设置UIWindowhidden 进行显示。这种方式有一个注意点,UIWindowrootViewController 是强引用,会和 VC 造成循环引用,在 Window 隐藏时,主动将 rootViewController 置为 nil, 打破循环。

使用 Window 还可以实现顺利启动广告图。

总结

可以将 App 看做一个容器,里面可以装多个 UIWindow,显示顺序和 windowLevel 的大小有关。
UIWindow 间彼此独立,不能相互添加,否者渲染层级会出问题。非keyWindowkeyWindow 间对系统事件的响应方式不同,通过 makeKeyWindow 可以调整多 Window 的显示,通过 makeKeyAndVisible 可以切换 keyWindow. 通过多 UIWindow,可以很方便的模仿 UIAlertView 的实现,实现自定义弹窗以及启动图。
Demo

喜欢和关注都是对我的鼓励和支持~
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,265评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,274评论 1 288
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,087评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,479评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,782评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,218评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,594评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,316评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,955评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,274评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,803评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,177评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,732评论 3 229
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,953评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,687评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,263评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,189评论 2 258