近期被一個 UIWindow 的問題坑慘了 ??,網(wǎng)上查了很久,沒什么資料,所以仔細再次深入研究了一下。
本文以問題的形式闡述,以下結(jié)論全部是看官方文檔以及自己試驗得出,如有錯誤,還望指出。
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 的作用?
接收鍵盤事件和其他非觸摸性事件。同一時間只有一個 keyWindow。
keyWindow 指的是哪一個 window?
最后一個調(diào)用 makeyAndVisible 或 makeyWindow 的 window
keyWindow 的 isHidden 屬性可能為 YES。如果一個 window 初始化之后,沒有調(diào)用 makeyAndVisible,直接調(diào)用 makeyWindow,就會出現(xiàn)這種情況
當前 keyWindow 被設(shè)置 hidden = YES,系統(tǒng)默認的下一個 keyWindow 是哪一個?
我們銷毀一個 window 的時候,往往這樣寫
self.window.hidden = YES;
self.window = nil;
如果這個 window 恰好是當前 keyWindow,調(diào)用 hidden = YES 的時候,系統(tǒng)將默認取消其 keyWindow,并在 hidden = YES 方法內(nèi)部設(shè)置一個新的 keyWindow
至于系統(tǒng)怎么設(shè)置新的 keyWindow
這是一個問題 ?? ??
經(jīng)過多番測試,找到如下規(guī)律:
- 一定是 windows 數(shù)組中,用戶創(chuàng)建的 level 最大的,但是 level 最大的可能有幾個,因為 level 可以相同
- level 相同的時候,一定是實際顯示在最上面的那個
總結(jié)一下就是,如果當前的 keyWindow 設(shè)置 hidden = YES,系統(tǒng)會去找 windows 數(shù)組中 實際顯示在最上層的那個 window
(當前的 keyWindow 可能就是實際顯示在最上層的那個,系統(tǒng)會將其跳過)
需要注意的是,實際顯示在最上面的不一定是 windows 最后一個元素,后面會講~
keyWindow 可能為空嗎? 2019.05.06 更新
目前知道以下情況,keyWindow 可能為空。
在使用 Main.storyboard 自動初始化根控制器,不手動去調(diào)用 AppDelegate 的 window 的 makeKeyAndVisible 或者 makeKeyWindow 的時候,是可能為空的。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
這個時候在上面這個方法沒有走完的時候,可以打印一下,keyWindow 是為 nil 的。只有等該方法走完之后,AppDelegate 的 window 才會變成 keyWindow。
需要注意的是,在 AppDelegate 的 window 還沒變成 keyWindow 之前,初始化其他 window,可能會造成 AppDelegate 的 window 顯示不正常。
所以盡量不在 didFinishLaunchingWithOptions 方法中初始化其他 window,實在需要的話,可以在初始化其他 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ù)組?
系統(tǒng)創(chuàng)建創(chuàng)建的 window(例如 status bar 的 window)不會被加到該數(shù)組
其他情況下,只要一個 window 被初始化了,就會自動加到 windows 數(shù)組
2019.05.06 更新
其實用加入這個詞語不太準確,經(jīng)過后期的一些了解,這個 windows 數(shù)組應(yīng)該是一個計算屬性,也就是說,在調(diào)用到該方法的時候,才去判斷哪些 window 應(yīng)該放到數(shù)組,返回回來。
如果實在不想自己的 window 加入到這個數(shù)組,可以繼承自 UIWindow,然后重寫下面這個私有方法(風險未知)
- (bool)isInternalWindow {
return YES;
}
windows 數(shù)組元素順序?
從整體上來看,windows 是根據(jù) window level 逆序排列的
但是如果 level 相同,就是后初始化的排在后面
如何改變 windows 元素順序?
調(diào)用 makeKeyWindow 或者 makeKeyAndVisible 都不會改變 windows 數(shù)組的順序,唯一一個改變的方法,就是改變一個 window 的 level
level 改變之后,當然也是按照 level 逆序排序
level 相同的,后初始化的排在后面(也許是通過比較內(nèi)存地址來判斷初始化順序 ??)
顯示在最上層的 window
怎么成為最上面的 window?
頂部的 window 肯定是 level 最高的 window,所以想成為最上面的,直接將 level 設(shè)置的很大即可
如果出現(xiàn) level 相同的,情況比較復(fù)雜
- 如果一個 window 在其他 window 調(diào)用完各種方法之后,調(diào)用
makeyAndVisible方法的可以在最上方,不管前面調(diào)用了什么方法,反正只要在最后調(diào)用makeyAndVisible,都可以提升到同等級的最上面 - 如果一個 window 從來沒有調(diào)用過
makeyAndVisible或hidden = NO,那么可以通過調(diào)用hidden = NO這句代碼,可以達到情況1同樣的效果
最上面的 window 是不是 windows 數(shù)組中最后一個元素?
大多數(shù)情況下是,但是有 level 相同的 window 的時候可能就不是了。
因為 window 無論怎么調(diào)用 makeyAndVisible 或者 makeKeyWindow 都是不會影響到一個 window 在 windows 數(shù)組中的位置,唯有改變 level 才可以。
當 level 確定之后,windows 數(shù)組中的元素順序肯定就不會變了
但是調(diào)用 makeyAndVisible 方法是可以改變同等級的 window 的上下關(guān)系的,所以最上面的 window 不一定就是 windows 數(shù)組中最后一個 window
說極端一點的話,甚至可能出現(xiàn)這樣的情況,一個 window 既在 windows 數(shù)組的最后一個,并且是 keyWindow,但是它實際上并不是顯示在最上面的那一個 window
<span id="a">如何取到真正的顯示在最上面的 window?</span>
這是一個問題 ?? ??
我只能說,如果我們忽略掉 level 相同的那種特殊情況,那我們大多數(shù)情況可以這么找,基本不會出問題
- (UIWindow *)topWindow
{
UIWindow *topWindow = nil;
NSArray <UIWindow *>*windows = [UIApplication sharedApplication].windows;
for (UIWindow *window in windows.reverseObjectEnumerator) {
if (window.hidden == YES || window.opaque == NO) {
// 隱藏的 或者 透明的 跳過
continue;
}
if (CGRectEqualToRect(window.bounds, [UIScreen mainScreen].bounds) == NO) {
// 不是全屏的 跳過
continue;
}
topWindow = window;
break;
}
return topWindow?:[UIApplication sharedApplication].delegate.window;
}
幾個方法
makeKeyAndVisible 和 makeKeyWindow 的區(qū)別?
makeKeyAndVisible 會改變顯示效果,并設(shè)置 keyWindow。其內(nèi)部調(diào)用了 makeKeyWindow,并且設(shè)置了 hidden = NO,除此之外應(yīng)該還有一些其他操作~
makeKeyWindow 只會設(shè)置 keyWindow
創(chuàng)建了一個 window,需要顯示,一定要調(diào)用 makeKeyAndVisible 嗎?
不一定,其實直接調(diào)用 hidden = NO,也可以顯示的。
官方是這樣說的:
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.
大概意思就是說,makeKeyAndVisible 方法就是為了方便,大多數(shù) app 調(diào)用這個方法來顯示 main window 并且 設(shè)置為 keyWindow,并且將其放在 level 小于等于它的 window 之上。如果只是想要顯示一個 window,直接修改它的 hidden 屬性,設(shè)置為 NO 就可以了。
那么其實,我們?nèi)绻胱屢粋€ window 顯示,并且不想讓它成為 keyWindow 的話,就直接設(shè)置
self.window.hidden = NO;
這樣是最好的
makeKeyWindow 什么時候會被調(diào)用?
makeKeyWindow 方法的作用就是將一個 window 設(shè)置為 keyWindow
- 調(diào)用
makeKeyAndVisible方法內(nèi)部會調(diào)用makeKeyWindow方法 - 設(shè)置 keyWindow 的
hidden = YES的時候,系統(tǒng)會找一個 window,設(shè)置為 keyWindow,所以也會調(diào)用makeKeyWindow
window 的 setHidden: 方法
一個 window 被創(chuàng)建的時候,其 hidden 屬性默認是為 YES 的。
當設(shè)置 hidden = NO 即可顯示一個 window,無需將 window 添加到一個 superview 上面。
當設(shè)置 hidden = YES 會將這個 window 隱藏。如果這個 window 是 keyWindow,那么這個 setHidden: 的方法內(nèi)部會取消當前 window 是 keyWindow 的設(shè)置,并且會找一個新的 window 來設(shè)置為 keyWindow
使用時的注意點
因為應(yīng)用內(nèi)長期濫用 window,很多彈窗浮層都是 window,并且有些第三方的 SDK 里面也搞了一些奇奇怪怪的 window。導(dǎo)致很多時候在獲取 window 會出現(xiàn)一些亂七八糟的問題,目前也沒有找到很好的方案。
所以,個人認為,如果不是必須使用 window,就最好不要使用 window,因為 window 多了之后,很不方便管理??梢允?view 或者 viewController 代替~
實在要用的時候,注意以下問題
添加一個視圖到最上層的 window
不要使用
[[UIApplication sharedApplication] keyWindow][[[UIApplication sharedApplication] windows] lastObject]
這兩個都不一定是最上層的 window,而且很有可能有問題,最終發(fā)現(xiàn)自己的視圖不知道添加到哪里去了,所以最好這樣找
這個方法不一定對,但是相對大部分情況下都是適用的,目前沒有找到更完美的解決方法
如果你只是想顯示一個 window,不想它變成 keyWindow
不要使用
[self.window makeKeyAndVisible];
直接設(shè)置 hidden = NO,即
self.window.hidden = NO;
這樣可以將它顯示出來,并且不會設(shè)置為 keyWindow。如果沒顯示正常,檢查一下 window level 是否正確。
如果想讓一個 window 永遠不要變成 keyWindow
最好是重寫 becomeKeyWindow 方法,調(diào)用 [super becomeKeyWindow] 之后,找到實際顯示在最上層的全屏 window,將其設(shè)置為 keyWindow
之前已經(jīng)說過,找實際顯示在最上層的 window 其實是有一定問題的(level 相同的情況),將就用吧 ?? ,實在沒找到更好的方法,如果你有有更好的辦法的話,請告訴我~
2019.05.06 更新
經(jīng)常一番研究,總算找到了讓一個 window 不變成 keyWindow 的方法,可以繼承 UIWindow,然后重寫以下私有方法(風險未知)
- (bool)_canBecomeKeyWindow {
return NO;
}
如何讓 window 不影響狀態(tài)欄 2019.05.06 更新
經(jīng)測試,顯示新的 window 有時候會影響到狀態(tài)欄的展示,為了徹底避免影響到狀態(tài)欄,可以繼承 UIWindow,然后重寫以下私有方法(風險未知)
- (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/