近期被一個(gè) UIWindow 的問(wèn)題坑慘了 ??碳褒,網(wǎng)上查了很久,沒(méi)什么資料寂恬,所以仔細(xì)再次深入研究了一下。
本文以問(wèn)題的形式闡述莱没,以下結(jié)論全部是看官方文檔以及自己試驗(yàn)得出初肉,如有錯(cuò)誤,還望指出饰躲。
UIApplication ★ keyWindow
The app's key window.
This property holds the UIWindow object in the windows array that is most recently sent the makeKeyAndVisible message.
keyWindow 的作用牙咏?
接收鍵盤事件和其他非觸摸性事件。同一時(shí)間只有一個(gè) keyWindow嘹裂。
keyWindow 指的是哪一個(gè) window妄壶?
最后一個(gè)調(diào)用 makeyAndVisible
或 makeyWindow
的 window
keyWindow 的 isHidden
屬性可能為 YES
。如果一個(gè) window 初始化之后寄狼,沒(méi)有調(diào)用 makeyAndVisible
丁寄,直接調(diào)用 makeyWindow
,就會(huì)出現(xiàn)這種情況
當(dāng)前 keyWindow 被設(shè)置 hidden = YES泊愧,系統(tǒng)默認(rèn)的下一個(gè) keyWindow 是哪一個(gè)伊磺?
我們銷毀一個(gè) window 的時(shí)候,往往這樣寫
self.window.hidden = YES;
self.window = nil;
如果這個(gè) window 恰好是當(dāng)前 keyWindow删咱,調(diào)用 hidden = YES
的時(shí)候屑埋,系統(tǒng)將默認(rèn)取消其 keyWindow,并在 hidden = YES
方法內(nèi)部設(shè)置一個(gè)新的 keyWindow
至于系統(tǒng)怎么設(shè)置新的 keyWindow
這是一個(gè)問(wèn)題 ?? ??
經(jīng)過(guò)多番測(cè)試痰滋,找到如下規(guī)律:
- 一定是 windows 數(shù)組中摘能,用戶創(chuàng)建的 level 最大的,但是 level 最大的可能有幾個(gè)敲街,因?yàn)?level 可以相同
- level 相同的時(shí)候团搞,一定是實(shí)際顯示在最上面的那個(gè)
總結(jié)一下就是,如果當(dāng)前的 keyWindow 設(shè)置 hidden = YES
多艇,系統(tǒng)會(huì)去找 windows 數(shù)組中 實(shí)際顯示在最上層的那個(gè) window
(當(dāng)前的 keyWindow 可能就是實(shí)際顯示在最上層的那個(gè)逻恐,系統(tǒng)會(huì)將其跳過(guò))
需要注意的是,實(shí)際顯示在最上面的不一定是 windows 最后一個(gè)元素,后面會(huì)講~
keyWindow 可能為空嗎梢莽? 2019.05.06 更新
目前知道以下情況,keyWindow 可能為空奸披。
在使用 Main.storyboard
自動(dòng)初始化根控制器昏名,不手動(dòng)去調(diào)用 AppDelegate 的 window 的 makeKeyAndVisible
或者 makeKeyWindow
的時(shí)候,是可能為空的阵面。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
這個(gè)時(shí)候在上面這個(gè)方法沒(méi)有走完的時(shí)候轻局,可以打印一下,keyWindow 是為 nil 的样刷。只有等該方法走完之后仑扑,AppDelegate 的 window 才會(huì)變成 keyWindow。
需要注意的是置鼻,在 AppDelegate 的 window 還沒(méi)變成 keyWindow 之前镇饮,初始化其他 window,可能會(huì)造成 AppDelegate 的 window 顯示不正常箕母。
所以盡量不在 didFinishLaunchingWithOptions
方法中初始化其他 window储藐,實(shí)在需要的話,可以在初始化其他 window 之前嘶是,先調(diào)用 AppDelegate 的 window 的 makeKeyAndVisible
方法钙勃。
UIApplication ★ windows
The app's visible and hidden windows.
This property contains the UIWindow objects currently associated with the app. This list does not include windows created and managed by the system, such as the window used to display the status bar.
The windows in the array are ordered from back to front by window level; thus, the last window in the array is on top of all other app windows.
window 什么時(shí)候會(huì)加入到這個(gè)數(shù)組?
系統(tǒng)創(chuàng)建創(chuàng)建的 window(例如 status bar 的 window)不會(huì)被加到該數(shù)組
其他情況下聂喇,只要一個(gè) window 被初始化了辖源,就會(huì)自動(dòng)加到 windows 數(shù)組
2019.05.06 更新
其實(shí)用加入這個(gè)詞語(yǔ)不太準(zhǔn)確,經(jīng)過(guò)后期的一些了解希太,這個(gè) windows 數(shù)組應(yīng)該是一個(gè)計(jì)算屬性克饶,也就是說(shuō),在調(diào)用到該方法的時(shí)候誊辉,才去判斷哪些 window 應(yīng)該放到數(shù)組彤路,返回回來(lái)。
如果實(shí)在不想自己的 window 加入到這個(gè)數(shù)組芥映,可以繼承自 UIWindow洲尊,然后重寫下面這個(gè)私有方法(風(fēng)險(xiǎn)未知)
- (bool)isInternalWindow {
return YES;
}
windows 數(shù)組元素順序?
從整體上來(lái)看奈偏,windows 是根據(jù) window level 逆序排列的
但是如果 level 相同坞嘀,就是后初始化的排在后面
如何改變 windows 元素順序?
調(diào)用 makeKeyWindow
或者 makeKeyAndVisible
都不會(huì)改變 windows 數(shù)組的順序惊来,唯一一個(gè)改變的方法丽涩,就是改變一個(gè) window 的 level
level 改變之后,當(dāng)然也是按照 level 逆序排序
level 相同的,后初始化的排在后面(也許是通過(guò)比較內(nèi)存地址來(lái)判斷初始化順序 ??)
顯示在最上層的 window
怎么成為最上面的 window矢渊?
頂部的 window 肯定是 level 最高的 window继准,所以想成為最上面的,直接將 level 設(shè)置的很大即可
如果出現(xiàn) level 相同的矮男,情況比較復(fù)雜
- 如果一個(gè) window 在其他 window 調(diào)用完各種方法之后移必,調(diào)用
makeyAndVisible
方法的可以在最上方,不管前面調(diào)用了什么方法毡鉴,反正只要在最后調(diào)用makeyAndVisible
崔泵,都可以提升到同等級(jí)的最上面 - 如果一個(gè) window 從來(lái)沒(méi)有調(diào)用過(guò)
makeyAndVisible
或hidden = NO
,那么可以通過(guò)調(diào)用hidden = NO
這句代碼猪瞬,可以達(dá)到情況1
同樣的效果
最上面的 window 是不是 windows 數(shù)組中最后一個(gè)元素?
大多數(shù)情況下是憎瘸,但是有 level 相同的 window 的時(shí)候可能就不是了。
因?yàn)?window 無(wú)論怎么調(diào)用 makeyAndVisible
或者 makeKeyWindow
都是不會(huì)影響到一個(gè) window 在 windows 數(shù)組中的位置陈瘦,唯有改變 level 才可以幌甘。
當(dāng) level 確定之后,windows 數(shù)組中的元素順序肯定就不會(huì)變了
但是調(diào)用 makeyAndVisible
方法是可以改變同等級(jí)的 window 的上下關(guān)系的痊项,所以最上面的 window 不一定就是 windows 數(shù)組中最后一個(gè) window
說(shuō)極端一點(diǎn)的話含潘,甚至可能出現(xiàn)這樣的情況,一個(gè) window 既在 windows 數(shù)組的最后一個(gè)线婚,并且是 keyWindow遏弱,但是它實(shí)際上并不是顯示在最上面的那一個(gè) window
<span id="a">如何取到真正的顯示在最上面的 window?</span>
這是一個(gè)問(wèn)題 ?? ??
我只能說(shuō)塞弊,如果我們忽略掉 level 相同的那種特殊情況漱逸,那我們大多數(shù)情況可以這么找,基本不會(huì)出問(wèn)題
- (UIWindow *)topWindow
{
UIWindow *topWindow = nil;
NSArray <UIWindow *>*windows = [UIApplication sharedApplication].windows;
for (UIWindow *window in windows.reverseObjectEnumerator) {
if (window.hidden == YES || window.opaque == NO) {
// 隱藏的 或者 透明的 跳過(guò)
continue;
}
if (CGRectEqualToRect(window.bounds, [UIScreen mainScreen].bounds) == NO) {
// 不是全屏的 跳過(guò)
continue;
}
topWindow = window;
break;
}
return topWindow?:[UIApplication sharedApplication].delegate.window;
}
幾個(gè)方法
makeKeyAndVisible 和 makeKeyWindow 的區(qū)別游沿?
makeKeyAndVisible
會(huì)改變顯示效果饰抒,并設(shè)置 keyWindow。其內(nèi)部調(diào)用了 makeKeyWindow
诀黍,并且設(shè)置了 hidden = NO
袋坑,除此之外應(yīng)該還有一些其他操作~
makeKeyWindow
只會(huì)設(shè)置 keyWindow
創(chuàng)建了一個(gè) window,需要顯示眯勾,一定要調(diào)用 makeKeyAndVisible 嗎枣宫?
不一定,其實(shí)直接調(diào)用 hidden = NO
吃环,也可以顯示的也颤。
官方是這樣說(shuō)的:
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.
大概意思就是說(shuō),makeKeyAndVisible
方法就是為了方便郁轻,大多數(shù) app 調(diào)用這個(gè)方法來(lái)顯示 main window
并且 設(shè)置為 keyWindow
翅娶,并且將其放在 level 小于等于它的 window 之上文留。如果只是想要顯示一個(gè) window,直接修改它的 hidden 屬性竭沫,設(shè)置為 NO 就可以了燥翅。
那么其實(shí),我們?nèi)绻胱屢粋€(gè) window 顯示蜕提,并且不想讓它成為 keyWindow 的話不皆,就直接設(shè)置
self.window.hidden = NO;
這樣是最好的
makeKeyWindow 什么時(shí)候會(huì)被調(diào)用最住?
makeKeyWindow 方法的作用就是將一個(gè) window 設(shè)置為 keyWindow
- 調(diào)用
makeKeyAndVisible
方法內(nèi)部會(huì)調(diào)用makeKeyWindow
方法 - 設(shè)置 keyWindow 的
hidden = YES
的時(shí)候癣缅,系統(tǒng)會(huì)找一個(gè) window蔗牡,設(shè)置為 keyWindow霎俩,所以也會(huì)調(diào)用makeKeyWindow
window 的 setHidden: 方法
一個(gè) window 被創(chuàng)建的時(shí)候危虱,其 hidden 屬性默認(rèn)是為 YES 的拌消。
當(dāng)設(shè)置 hidden = NO
即可顯示一個(gè) window置蜀,無(wú)需將 window 添加到一個(gè) superview 上面镣煮。
當(dāng)設(shè)置 hidden = YES
會(huì)將這個(gè) window 隱藏姐霍。如果這個(gè) window 是 keyWindow,那么這個(gè) setHidden:
的方法內(nèi)部會(huì)取消當(dāng)前 window 是 keyWindow 的設(shè)置典唇,并且會(huì)找一個(gè)新的 window 來(lái)設(shè)置為 keyWindow
使用時(shí)的注意點(diǎn)
因?yàn)閼?yīng)用內(nèi)長(zhǎng)期濫用 window镊折,很多彈窗浮層都是 window,并且有些第三方的 SDK 里面也搞了一些奇奇怪怪的 window介衔。導(dǎo)致很多時(shí)候在獲取 window 會(huì)出現(xiàn)一些亂七八糟的問(wèn)題恨胚,目前也沒(méi)有找到很好的方案。
所以炎咖,個(gè)人認(rèn)為赃泡,如果不是必須使用 window,就最好不要使用 window乘盼,因?yàn)?window 多了之后升熊,很不方便管理〕裾ぃ可以使 view 或者 viewController 代替~
實(shí)在要用的時(shí)候级野,注意以下問(wèn)題
添加一個(gè)視圖到最上層的 window
不要使用
[[UIApplication sharedApplication] keyWindow]
[[[UIApplication sharedApplication] windows] lastObject]
這兩個(gè)都不一定是最上層的 window,而且很有可能有問(wèn)題粹胯,最終發(fā)現(xiàn)自己的視圖不知道添加到哪里去了蓖柔,所以最好這樣找
這個(gè)方法不一定對(duì)风纠,但是相對(duì)大部分情況下都是適用的渊抽,目前沒(méi)有找到更完美的解決方法
如果你只是想顯示一個(gè) window,不想它變成 keyWindow
不要使用
[self.window makeKeyAndVisible];
直接設(shè)置 hidden = NO
议忽,即
self.window.hidden = NO;
這樣可以將它顯示出來(lái)懒闷,并且不會(huì)設(shè)置為 keyWindow。如果沒(méi)顯示正常,檢查一下 window level 是否正確愤估。
如果想讓一個(gè) window 永遠(yuǎn)不要變成 keyWindow
最好是重寫 becomeKeyWindow 方法帮辟,調(diào)用 [super becomeKeyWindow]
之后,找到實(shí)際顯示在最上層的全屏 window玩焰,將其設(shè)置為 keyWindow
之前已經(jīng)說(shuō)過(guò)由驹,找實(shí)際顯示在最上層的 window 其實(shí)是有一定問(wèn)題的(level 相同的情況),將就用吧 ?? 昔园,實(shí)在沒(méi)找到更好的方法蔓榄,如果你有有更好的辦法的話,請(qǐng)告訴我~
2019.05.06 更新
經(jīng)常一番研究默刚,總算找到了讓一個(gè) window 不變成 keyWindow 的方法甥郑,可以繼承 UIWindow,然后重寫以下私有方法(風(fēng)險(xiǎn)未知)
- (bool)_canBecomeKeyWindow {
return NO;
}
如何讓 window 不影響狀態(tài)欄 2019.05.06 更新
經(jīng)測(cè)試荤西,顯示新的 window 有時(shí)候會(huì)影響到狀態(tài)欄的展示澜搅,為了徹底避免影響到狀態(tài)欄,可以繼承 UIWindow邪锌,然后重寫以下私有方法(風(fēng)險(xiǎn)未知)
- (bool)_canAffectStatusBarAppearance {
return NO;
}
參考
- https://developer.apple.com/documentation/uikit/uiapplication/1622924-keywindow?language=objc
- https://developer.apple.com/documentation/uikit/uiapplication/1623104-windows?language=objc
- https://developer.apple.com/documentation/uikit/uiwindow?language=objc#
- https://developer.apple.com/documentation/uikit/uiwindow/1621601-makekeyandvisible?language=objc
- https://bugtags.kf5.com/hc/kb/article/77692/
- https://jkyin.me/uiwindow/