UIWindow 簡介
一個(gè)UIWindow對象為應(yīng)用程序的用戶界面提供了背景以及重要的事件處理行為。
UIWindow繼承自UIView钝腺,我們一般不會直接去設(shè)置其UI展現(xiàn)攘宙,但它對展現(xiàn)程序中的views至關(guān)重要床嫌。每一個(gè)view蹋辅,想要出現(xiàn)在屏幕上都依賴于window,但是程序中的window之間是相互獨(dú)立的窗宇。應(yīng)用程序收到事件之后會先轉(zhuǎn)發(fā)給適當(dāng)?shù)膚indow對象措伐,從而又將事件轉(zhuǎn)發(fā)給view對象。
程序中有哪些 UIWindow
所有view的展現(xiàn)都依賴于window担映,創(chuàng)建一個(gè)新的iOS工程废士,將其運(yùn)行會執(zhí)行以下事情
- Xcode會自動創(chuàng)建一個(gè)window,即
app delegate
中的window屬性蝇完。 - 同時(shí),Xcode會默認(rèn)創(chuàng)建一個(gè)Main.storyboard,其
instantiateInitialViewController
的顯示需要window官硝,依賴的即為前面的window。 - 此時(shí)短蜕,該window的
rootViewController
即為Main.storyboard的instantiateInitialViewController
氢架。
很顯然,一個(gè)應(yīng)用程序當(dāng)中朋魔,不是只能有一個(gè)window岖研,可以存在多個(gè)window。已知的有以下window:
- app delegate里的window
- 狀態(tài)欄的window(比較特殊,雖然在程序內(nèi)部可以調(diào)用某些api顯示隱藏或改變其UI孙援,但它的window是不被我們的應(yīng)用程序內(nèi)部所持有的)
- 鍵盤的window
獲取程序中的 UIWindow
UIApplication
這個(gè)類是一個(gè)單例類害淤,通過其sharedApplication方法進(jìn)行調(diào)用,一個(gè)程序可以看做是一個(gè)UIApplication對象,可以通過UIApplication對象的以下屬性來獲取想要的window拓售。
@property(nullable, nonatomic,readonly) UIWindow *keyWindow;
@property(nonatomic,readonly) NSArray<__kindof UIWindow *> *windows;
keyWindow 應(yīng)用程序的關(guān)鍵 window窥摄,官方描述如下
This property holds the UIWindow object in the windows array that is most recently sent the makeKeyAndVisible message.
用來接收鍵盤以及非觸摸類的消息事件的 UIWindow,而且程序中每個(gè)時(shí)刻只能有一個(gè) UIWindow 是 keyWindow础淤。同時(shí)也是最后一個(gè)調(diào)用 makeKeyAndVisible
方法的 UIWindow崭放。
windows 應(yīng)用程序中所有的非系統(tǒng)創(chuàng)建的 window 對象,官方描述如下
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.
例如 sutatus bar 不會包含在其中鸽凶。會包含所有用戶創(chuàng)建的 Window币砂,也就是說包括正在顯示的或隱藏的 window,同時(shí)這個(gè)數(shù)組的裴谞是根據(jù) window level 進(jìn)行排序的玻侥,從后往前進(jìn)行排序决摧,排在最后的證明 window 層級最高。
新建一個(gè)iOS工程,在沒有觸發(fā)鍵盤時(shí)使碾,在控制臺打印winodws
如下:
(lldb) po [[UIApplication sharedApplication] windows]
<__NSArrayM 0x61800024d7d0>(
<**UIWindow**: 0x7fd8e1a06370; frame = (0 0; 414 736); autoresize = W+H; gestureRecognizers = <NSArray: 0x61800024d170>; layer = <UIWindowLayer: 0x61000003e7c0>>
)
該window就是 app delegate 的window蜜徽,即系統(tǒng)自動生成的那個(gè)window祝懂。當(dāng)文本編輯票摇,觸發(fā)鍵盤之后,打印windows
如下:
(lldb) po [[UIApplication sharedApplication] windows]
<__NSArrayM 0x61000005d1f0>(
<**UIWindow**: 0x7fd8e1a06370; frame = (0 0; 414 736); autoresize = W+H; gestureRecognizers = <NSArray: 0x61800024d170>; layer = <UIWindowLayer: 0x61000003e7c0>>,
<**UITextEffectsWindow**: 0x7fd8dfc1cde0; frame = (0 0; 414 736); opaque = NO; autoresize = W+H; layer = <UIWindowLayer: 0x60800022dd60>>,
<**UIRemoteKeyboardWindow**: 0x7fd8dfc27e40; frame = (0 0; 414 736); opaque = NO; autoresize = W+H; layer = <UIWindowLayer: 0x608000231300>>
)
打印中的 UIRemoteKeyboardWindow
就是鍵盤的window砚蓬,與此同時(shí)矢门,還出現(xiàn)了UITextEffectsWindow
,這個(gè)window我沒有找到官方的說明灰蛙,不過可以推測它也是和文本輸入有關(guān)系的祟剔。
UIWindow 的屬性與方法
@property(nonatomic,strong) UIScreen *screen
該屬性默認(rèn)為[UIScreen mainScreen]
,一個(gè)UIScreen
對象對應(yīng)一個(gè)實(shí)際設(shè)備的物理屏幕摩梧,一般情況下物延,我們不需要對其進(jìn)行設(shè)置。一個(gè)iPhone默認(rèn)也就一個(gè)屏幕仅父,一個(gè)屏幕可以存在多個(gè)window叛薯,那也是為什么我們一個(gè)程序里面可以有多個(gè)window的原因。
當(dāng)一個(gè)iPhone連接一個(gè)外接屏幕的時(shí)候笙纤,系統(tǒng)會發(fā)送通知耗溜。然而如果我們什么都不做,外接屏幕會一片漆黑省容,因?yàn)樵谀莻€(gè)屏幕上不存在任何window對象抖拴。如果真的想要在外接的屏幕中顯示一些東西的話,那就應(yīng)該監(jiān)聽系統(tǒng)通知腥椒,在接收通知的方法里創(chuàng)建一個(gè)新的window阿宅,并將其顯示候衍,當(dāng)然,斷開連接的時(shí)候洒放,應(yīng)該將window對象置為nil釋放脱柱。以下為官方示例代碼:
- (void)handleScreenConnectNotification:(NSNotification*)aNotification {
UIScreen* newScreen = [aNotification object];
CGRect screenBounds = newScreen.bounds;
if (!_secondWindow) {
_secondWindow = [[UIWindow alloc] initWithFrame:screenBounds];
_secondWindow.screen = newScreen;
// Set the initial UI for the window and show it.
[self.viewController displaySelectionInSecondaryWindow:_secondWindow];
[_secondWindow makeKeyAndVisible];
}
}
- (void)handleScreenDisconnectNotification:(NSNotification*)aNotification {
if (_secondWindow) {
// Hide and then delete the window.
_secondWindow.hidden = YES;
[_secondWindow release];
_secondWindow = nil;
// Update the main screen based on what is showing here.
[self.viewController displaySelectionOnMainScreen];
}
}
@property(nonatomic) UIWindowLevel windowLevel;
window等級,即window在z軸上的層級關(guān)系,默認(rèn)是0拉馋。UIWindowLevel
本身是一個(gè)CGFloat
類型,可以隨意設(shè)置或進(jìn)行加減榨为,高等級會顯示在低等級上面。系統(tǒng)給出了三種常用等級:
UIKIT_EXTERN const UIWindowLevel UIWindowLevelNormal; 0
UIKIT_EXTERN const UIWindowLevel UIWindowLevelAlert; 2000
UIKIT_EXTERN const UIWindowLevel UIWindowLevelStatusBar; 4000
@property(nonatomic,readonly,getter=isKeyWindow) BOOL keyWindow;
當(dāng)前window對象是否為程序的keyWindow煌茴,系統(tǒng)會自動賦值更新随闺,我們不需要,也不能手動設(shè)置蔓腐。
- (void)becomeKeyWindow;// override point for subclass. Do not call directly
- (void)resignKeyWindow;// override point for subclass. Do not call directly
這個(gè)是在繼承的時(shí)候進(jìn)行重寫的矩乐,不要手動去調(diào)用。在一個(gè)window的keyWindow屬性改變時(shí)會調(diào)用回论,當(dāng)你寫一個(gè)子類繼承UIWindow,如果需要在window變成keyWindow,或是keyWinow變?yōu)镹O的時(shí)候想做一些事情散罕,就可以重寫這兩個(gè)方法,以下為官方解釋傀蓉。
You should rarely need to subclass UIWindow. The kinds of behaviors you might implement in a window can usually be implemented in a higher-level view controller more easily. One of the few times you might want to subclass is to override the becomeKeyWindow or resignKeyWindow methods to implement custom behaviors when a window’s key status changes.
- (void)makeKeyWindow;
- (void)makeKeyAndVisible;
一個(gè)window的hideen屬性默認(rèn)是YES的欧漱,makeKeyWindow
是將一個(gè)window設(shè)置為keyWindow,但是makeKeyAndVisible
會將一個(gè)window設(shè)置為keyWindow并將其顯示葬燎。如何沒有變成keyWindow,則其內(nèi)部的文本框沒法輸入文字误甚。
UIWindow: 0x12dd3ef20; frame = (0 200; 200 200); hidden = YES; gestureRecognizers = <NSArray: 0x12dd40530>; layer = <UIWindowLayer: 0x12dd3f230>
UIWindow: 0x12dd3ef20; frame = (0 200; 200 200); gestureRecognizers = <NSArray: 0x12dd40530>; layer = <UIWindowLayer: 0x12dd3f230>
以上為將一個(gè)window調(diào)用makeKeyAndVisible
前后對比,可以發(fā)現(xiàn)谱净,其hidden從YES變?yōu)镹O窑邦。所以某個(gè)window調(diào)用makeKeyAndVisible
之后,系統(tǒng)對該window至少做了以下事情:
- 將UIApplication對象的keyWindow設(shè)置為當(dāng)前這個(gè)window
- 當(dāng)前window的
hidden
設(shè)置為NO壕探,同時(shí)該window的keyWindow屬性變?yōu)閅ES
@property(nullable, nonatomic,strong) UIViewController *rootViewController;
該屬性為window的根控制器冈钦,現(xiàn)在這個(gè)屬性是不能為空的,必須進(jìn)行賦值李请,否則程序會崩潰瞧筛。
- (void)sendEvent:(UIEvent *)event;
有事件需要處理的時(shí)候UIApplication會調(diào)用該方法派發(fā)事件。
- (CGPoint)convertPoint:(CGPoint)point toWindow:(nullable UIWindow *)window;
- (CGPoint)convertPoint:(CGPoint)point fromWindow:(nullable UIWindow *)window;
- (CGRect)convertRect:(CGRect)rect toWindow:(nullable UIWindow *)window;
- (CGRect)convertRect:(CGRect)rect fromWindow:(nullable UIWindow *)window;
window之間是相互獨(dú)立的捻艳,如果想要將兩個(gè)window的坐標(biāo)相互映射的時(shí)候驾窟,就需要用到以上幾個(gè)方法。
如何創(chuàng)建一個(gè) UIWindow 并顯示
主要有以下幾個(gè)步驟:
- 創(chuàng)建一個(gè)window對象认轨,并用一個(gè)對象強(qiáng)持有它
- 創(chuàng)建一個(gè)控制器绅络,賦值為window的根控制器
- 顯示窗口
代碼如下:
//1. 創(chuàng)建一個(gè)window對象,并用一個(gè)對象強(qiáng)持有它
UIWindow *testWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.testWindow = testWindow;
//2. 創(chuàng)建一個(gè)控制器,賦值為window的根控制器
UIViewController *controller = [[UIViewController alloc] init];
testWindow.rootViewController = controller;
//3. 顯示窗口
[testWindow makeKeyAndVisible];
這里恩急,需要注意一下:
- window的frame決定了這個(gè)窗口大小杉畜,所以需要進(jìn)行設(shè)置
- 新的控制器之所以能正常顯示,是因?yàn)閣indow強(qiáng)持有它衷恭,window能正常運(yùn)行此叠,則是因?yàn)槲覀冇昧艘粋€(gè)暫時(shí)不會銷毀的對象強(qiáng)持有window(當(dāng)然,直接用一個(gè)靜態(tài)變量持有也可以随珠,本質(zhì)上是一樣的)灭袁。
- 如果window看不見,可以試試修改以下其
windowLevel
屬性窗看。高等級的window一定會顯示在低等級的window上面茸歧,同等級的window,后makeKeyWindow的window就會顯示在上面。 - 無論是通過代碼,storyboard或xib初始化一個(gè)控制器來顯示显沈,都是以上三步软瞎,只是創(chuàng)建控制器的方法有所區(qū)別罷了,這里不做討論了拉讯。
如何銷毀一個(gè) UIWindow
前面已經(jīng)說過涤浇,對于一個(gè)UIWindow對象,之所以顯示魔慷,是因?yàn)橛幸粋€(gè)對象強(qiáng)持有它只锭,要銷毀一個(gè)window,只需要將這個(gè)強(qiáng)持有去掉即可盖彭。但是,這種持有去掉之后纹烹,可能window可能不會立即消失页滚,所以召边,為了確保能夠立即將其不展現(xiàn),最好按以下步驟:
- 將window的hidden屬性置為YES
- 將持有該window的那個(gè)對象對window的持有去掉(有點(diǎn)繞??)
代碼如下:
self.testWindow.hidden = YES;
self.testWindow = nil;
<del>假如當(dāng)前這個(gè)window是keyWindow裹驰,這個(gè)window被銷毀之后隧熙,系統(tǒng)會自動將上一個(gè)keyWindow設(shè)置為keyWindow,不需要我們?nèi)ス芾砘昧帧:唵握f就是假如以 A->B->C 這個(gè)順序變?yōu)閗eyWindow之后贞盯,C銷毀了,B會自動變?yōu)閗eyWindow沪饺。</del>需要注意的是躏敢,不要去調(diào)用resignKeyWindow
方法,該方法是用于子類重寫的整葡,手動調(diào)用之后件余,結(jié)果也是未知的。
我們什么時(shí)候需要自己創(chuàng)建一個(gè) UIWindow
蘋果官方是這么說的??
Most apps need only one window, which displays the app’s content on the device’s main screen. You can create additional windows and display them on the device’s main screen, but extra windows are more commonly used to display content on an attached external display.
新建的UIWindow一般用于外接的屏幕,那在我們手機(jī)的主屏幕什么時(shí)候會有這種需求呢啼器?我覺得旬渠,如果我們需要個(gè)一個(gè)控件,需要獨(dú)立于其他的view,并懸浮于應(yīng)用程序中的時(shí)候端壳,也許就需要用到UIWindow了告丢,這里所謂的懸浮,不過就是windowLevel比較高罷了损谦。
公司工程里所集成的測試控件Bugtags就是利用UIWindow實(shí)現(xiàn)的岖免,可以懸浮在任意頁面,主要用于測試人員提bug照捡,直接手機(jī)上提bug觅捆。當(dāng)然提bug這件事和本文關(guān)系不大,在此只是想表明這種情況就可以用UIWindow麻敌。
關(guān)于懸浮球
對于這個(gè)可拉拽的懸浮球栅炒,我也比較好奇,所以自己著手實(shí)現(xiàn)了一下术羔,原理也挺簡單赢赊。
- 創(chuàng)建一個(gè)按鈕大小的window并顯示
- 將其windowLevel設(shè)置得較高
- 在按鈕上添加拖拽手勢,隨著手勢移動级历,并添加一些邊界控制
那就有人問了释移,這個(gè)東西有什么用?
因?yàn)楣镜墓こ汤锎_實(shí)沒有什么需要需要用到這個(gè)東西寥殖,但是我后來發(fā)現(xiàn)這個(gè)東西還是有那么一點(diǎn)用??玩讳。不過不是用在正式代碼之中,而是開發(fā)測試階段嚼贡。
- 做個(gè)一鍵登陸功能(公司的項(xiàng)目開發(fā)需要頻繁換號熏纯,輸密碼太麻煩)
- 如果不用換賬號,直接寫死一個(gè)賬號粤策,點(diǎn)擊懸浮球直接登錄
- 如果需要頻繁換賬號的樟澜,可以把登錄過的賬號都記錄下來,寫到NSUserDefaults等地方叮盘,以后每次需要登陸時(shí)秩贰,點(diǎn)擊浮球,出來一個(gè)列表柔吼,選其中一個(gè)登陸
- 一些調(diào)試的時(shí)候想要反復(fù)執(zhí)行的某句代碼
?具體細(xì)節(jié)可到GitHub下載demo查看毒费。GitHub地址 ??
?如果有用,還望朋友能給個(gè)star愈魏,謝謝觅玻。