查看滴滴開(kāi)源的 DoraemonKit 以及阿里開(kāi)源的youku-sdk-tool-woodpecker
時(shí), 看到啟動(dòng)入口均采用 UIWindow
來(lái)實(shí)現(xiàn), 效果如下圖懸浮的綠色按鈕.
拖動(dòng)懸浮按鈕可以隨手指一動(dòng)(圖上為模擬器效果), 點(diǎn)擊懸浮按鈕可以切換頁(yè)面.
對(duì)這種效果的實(shí)現(xiàn)方式, 我的第一直覺(jué)是采用在 VC 的 touch
事件中進(jìn)行處理, 但是按鈕還需要有點(diǎn)擊效果, 這個(gè)就難以處理了. 帶著疑問(wèn)查看源碼, 發(fā)現(xiàn)兩個(gè)項(xiàng)目中的懸浮框均是采用 UIWindow
的方式來(lái)實(shí)現(xiàn), 十分巧妙. 自己關(guān)于Window
的使用方式做了一些嘗試, 遂有此文.
1 新建的 UIWindow
如何顯示?
開(kāi)始時(shí), 我首先把創(chuàng)建的 UIWindow
添加到 keyWindow
上, 調(diào)用 hidden=NO
方法顯示正常(注意window實(shí)例要被對(duì)象持有呀页,避免創(chuàng)建后就銷毀), 但是在使用 Xcode
中的渲染工具debug view hierarchy
查看 App 的渲染層級(jí)時(shí), 直接崩潰. 反復(fù)查找, 移除添加到 keyWindow
的代碼, 顯示和渲染均正常.
UIWindow
屬于一個(gè)完整渲染層的容器控件, 不能再添加其他 UIWindow
實(shí)例. 一個(gè) App
應(yīng)用屬于一個(gè)最底層容器, 可以同時(shí)承載不同的 UIWindow
. 多個(gè) UIWindow
在App
中的顯示順序, 按照 windowLevel
的大小, 從前到后進(jìn)行顯示. App
中多個(gè) UIWindow
的響應(yīng), 和 View
中多個(gè) 子View
的響應(yīng)方式一樣, hitTest
方法返回哪個(gè)UIView
, 哪個(gè)對(duì)象就響應(yīng).
iOS13
中使用多場(chǎng)景 SceneDelegate
時(shí), 在 - (void)scene: willConnectToSession: options:
方法中創(chuàng)建 Window
時(shí), 要采用 initWithWindowScene:
的方式, 否則 Window
將無(wú)法顯示. 可以采用修改 Xcode
模板的方式, 在新建工程時(shí)默認(rèn)去除 SceneDelegate
, 操作方式可參考:MyTemplate.
2 keyWindow 和 delegate 的 Window
一般在 application:didFinishLaunchingWithOptions:
方法中新建 Window
, 作為 AppDelegate
的屬性. 然后調(diào)用 makeKeyAndVisible
方法顯示在 App 中. 此時(shí)通過(guò)[UIApplication sharedApplication].keyWindow
獲取的為此 Window
. 新建 UIWindow
實(shí)例, 調(diào)用makeKeyAndVisible
后, 可以改變項(xiàng)目中的 keyWindow
. 比如系統(tǒng)的 UIAlertView
/UIAlertController
/KeyBoard
顯示時(shí), 會(huì)自動(dòng)切換 UIApplication
的 keyWindow
. 將某些控件顯示在 keyWindow
時(shí), 需要注意變化, 避免控件意料之外的消失.
將UIWindow
的 hidden
屬性設(shè)置為 false
時(shí), 即可在 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.
主要在第二點(diǎn), 設(shè)置為 key window
后, 可以響應(yīng)鍵盤(pán)和非觸摸事件. 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
新增, 文檔上也沒(méi)有介紹. 日常開(kāi)發(fā)中接觸到的主要是前面 4 類. 第一類為 touch
事件, 是否為 keyWindow
均可以響應(yīng); 第二類為加速計(jì)事件, 主要指shake
; 第三類為遠(yuǎn)程控制, 比如airpod
對(duì)音量的控制; 第四類為按壓事件, 文檔上解釋為a physical button
, iPhone
上符合條件的只有音量鍵和電源鍵.
UIWindow
在顯示/隱藏浸船、keyWindow 切換時(shí)收捣,會(huì)發(fā)送以下 4 種通知:
UIWindowDidBecomeVisibleNotification
UIWindowDidBecomeHiddenNotification
UIWindowDidBecomeKeyNotification
UIWindowDidResignKeyNotification
監(jiān)聽(tīng)以上通知,測(cè)試鍵盤(pán)和2/3/4 類事件發(fā)生時(shí)鄙币,多 UIWindow
的響應(yīng)情況。粘貼代碼過(guò)于繁瑣,直接說(shuō)下測(cè)試結(jié)論:
-
motion
中的shake
事件中徽千,keyWindow
才可以響應(yīng); - 監(jiān)聽(tīng)音量鍵通知
AVSystemController_SystemVolumeDidChangeNotification
前汤锨,需要調(diào)用[[UIApplication sharedApplication] beginReceivingRemoteControlEvents]
方法双抽,非keyWindow
也可以正常監(jiān)聽(tīng), 這和文檔介紹的不同; - 非
keyWindow
中的UITextField
和UITextView
控件響應(yīng)時(shí), 會(huì)自動(dòng)將所在的Window
置位keyWindow
.
makeKeyWindow
和 makeKeyAndVisible
不同闲礼,看下 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
會(huì)把當(dāng)前 Window
放在其他同級(jí)別/低級(jí)別的 Window
前面牍汹,但是對(duì)事件響應(yīng)沒(méi)有絲毫影響铐维。當(dāng)keyWindow
被隱藏后,系統(tǒng)會(huì)自動(dòng)把級(jí)別最高的 Window
置為 keyWindow
.
3 多 Window
的使用
開(kāi)頭的例子慎菲,將懸浮框看做 UIWindow
的實(shí)例嫁蛇,可以響應(yīng) 觸摸事件。添加 panGesture
手勢(shì)露该,在響應(yīng)方法中睬棚,更新 UIWindow
的位置。同時(shí)在 UIWindow
添加 UIButton
控件解幼,進(jìn)而在點(diǎn)擊方法中進(jìn)行頁(yè)面操作抑党。自己簡(jiǎn)單實(shí)現(xiàn)了一下 FloatingView
使用 UIWindow
可以仿照系統(tǒng) UIAlertView
的實(shí)現(xiàn)方式,隨心所欲的 show
到最頂層撵摆。將 VC
上添加一個(gè) UIWindow
屬性(創(chuàng)建時(shí)默認(rèn)為屏幕大小)底靠,自身作為UIWindow
的根控制器,設(shè)置UIWindow
的 hidden
進(jìn)行顯示特铝。這種方式有一個(gè)注意點(diǎn)暑中,UIWindow
對(duì) rootViewController
是強(qiáng)引用,會(huì)和 VC
造成循環(huán)引用鲫剿,在 Window
隱藏時(shí)鳄逾,主動(dòng)將 rootViewController
置為 nil, 打破循環(huán)。
使用 Window
還可以實(shí)現(xiàn)順利啟動(dòng)廣告圖牵素。
總結(jié)
可以將 App 看做一個(gè)容器严衬,里面可以裝多個(gè) UIWindow
,顯示順序和 windowLevel
的大小有關(guān)笆呆。
多 UIWindow
間彼此獨(dú)立请琳,不能相互添加,否者渲染層級(jí)會(huì)出問(wèn)題赠幕。非keyWindow
和 keyWindow
間對(duì)系統(tǒng)事件的響應(yīng)方式不同俄精,通過(guò) makeKeyWindow
可以調(diào)整多 Window
的顯示,通過(guò) makeKeyAndVisible
可以切換 keyWindow
. 通過(guò)多 UIWindow
榕堰,可以很方便的模仿 UIAlertView
的實(shí)現(xiàn)竖慧,實(shí)現(xiàn)自定義彈窗以及啟動(dòng)圖。
Demo