帶測試小項目的demo下載
1.UIWindow簡介
一個UIWindow對象為應(yīng)用程序的用戶界面提供了背景以及重要的事件處理行為。
UIWindow繼承自UIView,我們一般不會直接去設(shè)置其UI展現(xiàn)每篷,但它對展現(xiàn)程序中的views至關(guān)重要。每一個view跑揉,想要出現(xiàn)在屏幕上都依賴于window惨好,但是程序中的window之間是相互獨(dú)立的。應(yīng)用程序收到事件之后會先轉(zhuǎn)發(fā)給適當(dāng)?shù)膚indow對象忽舟,從而又將事件轉(zhuǎn)發(fā)給view對象双妨。
2.程序中有哪些UIWindow
所有view的展現(xiàn)都依賴于window,創(chuàng)建一個新的iOS工程叮阅,將其運(yùn)行會執(zhí)行以下事情
Xcode會自動創(chuàng)建一個window刁品,即app delegate中的window屬性。
同時,Xcode會默認(rèn)創(chuàng)建一個Main.storyboard,其instantiateInitialViewController的顯示需要window浩姥,依賴的即為前面的window挑随。
此時,該window的rootViewController即為Main.storyboard的instantiateInitialViewController勒叠。
很顯然兜挨,一個應(yīng)用程序當(dāng)中,不是只能有一個window眯分,可以存在多個window拌汇。已知的有以下window:
app delegate里的window
狀態(tài)欄的window(比較特殊,雖然在程序內(nèi)部可以調(diào)用某些api顯示隱藏或改變其UI弊决,但它的window是不被我們的應(yīng)用程序內(nèi)部所持有的)
鍵盤的window
3.獲取程序中的UIWindow
UIApplication這個類是一個單例類噪舀,通過其sharedApplication方法進(jìn)行調(diào)用,一個程序可以看做是一個UIApplication對象,可以通過UIApplication對象的以下屬性來獲取想要的window丢氢。
@property(nullable, nonatomic,readonly) UIWindow *keyWindow;
@property(nonatomic,readonly) NSArray<__kindof UIWindow *> *windows;
keyWindow 應(yīng)用程序的關(guān)鍵window傅联。用來接收鍵盤以及非觸摸類的消息事件的UIWindow,而且程序中每個時刻只能有一個UIWindow是keyWindow疚察。
windows 應(yīng)用程序中所有的window對象蒸走,包括正在顯示的或隱藏的window。
不過在APP需要在不同程序之間進(jìn)行跳轉(zhuǎn)的時候,要想取得當(dāng)前正在顯示的window,其實(shí)可以使用
UIWindow *appWindow = [UIApplication sharedApplication].delegate.window;
// 獲取拖拽手勢在當(dāng)前顯示的Windows上的坐標(biāo)
CGPoint panPoint = [p locationInView:appWindow];
新建一個iOS工程,在沒有觸發(fā)鍵盤時貌嫡,在控制臺打印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)自動生成的那個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,與此同時夫椭,還出現(xiàn)了UITextEffectsWindow掸掸,這個window我沒有找到官方的說明,不過可以推測它也是和文本輸入有關(guān)系的。
4.UIWindow的屬性與方法
@property(nonatomic,strong) UIScreen *screen
該屬性默認(rèn)為[UIScreen mainScreen]扰付,一個UIScreen對象對應(yīng)一個實(shí)際設(shè)備的物理屏幕堤撵,一般情況下,我們不需要對其進(jìn)行設(shè)置羽莺。一個iPhone默認(rèn)也就一個屏幕实昨,一個屏幕可以存在多個window,那也是為什么我們一個程序里面可以有多個window的原因盐固。
當(dāng)一個iPhone連接一個外接屏幕的時候荒给,系統(tǒng)會發(fā)送通知。然而如果我們什么都不做刁卜,外接屏幕會一片漆黑志电,因為在那個屏幕上不存在任何window對象。如果真的想要在外接的屏幕中顯示一些東西的話长酗,那就應(yīng)該監(jiān)聽系統(tǒng)通知溪北,在接收通知的方法里創(chuàng)建一個新的window,并將其顯示夺脾,當(dāng)然,斷開連接的時候茉继,應(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本身是一個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
這個是在繼承的時候進(jìn)行重寫的镀赌,不要手動去調(diào)用。在一個window的keyWindow屬性改變時會調(diào)用际跪,當(dāng)你寫一個子類繼承UIWindow,如果需要在window變成keyWindow,或是keyWinow變?yōu)镹O的時候想做一些事情商佛,就可以重寫這兩個方法,以下為官方解釋姆打。
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;
一個window的hideen屬性默認(rèn)是YES的良姆,makeKeyWindow是將一個window設(shè)置為keyWindow,但是makeKeyAndVisible會將一個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>
以上為將一個window調(diào)用makeKeyAndVisible前后對比,可以發(fā)現(xiàn)闲延,其hidden從YES變?yōu)镹O痊剖。所以某個window調(diào)用makeKeyAndVisible之后韩玩,系統(tǒng)對該window至少做了以下事情:
將UIApplication對象的keyWindow設(shè)置為當(dāng)前這個window
當(dāng)前window的hidden設(shè)置為NO,同時該window的keyWindow屬性變?yōu)閅ES
@property(nullable, nonatomic,strong) UIViewController *rootViewController;
該屬性為window的根控制器邢笙,現(xiàn)在這個屬性是不能為空的啸如,必須進(jìn)行賦值,否則程序會崩潰
- (void)sendEvent:(UIEvent *)event;
有事件需要處理的時候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ú)立的叮雳,如果想要將兩個window的坐標(biāo)相互映射的時候,就需要用到以上幾個方法妇汗。
5.如何創(chuàng)建一個UIWindow并顯示
主要有以下幾個步驟:
創(chuàng)建一個window對象帘不,并用一個對象強(qiáng)持有它,創(chuàng)建一個控制器,賦值為,window的根控制器顯示窗口
代碼如下:
//1. 創(chuàng)建一個window對象杨箭,并用一個對象強(qiáng)持有它
UIWindow *testWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.testWindow = testWindow;
//2. 創(chuàng)建一個控制器寞焙,賦值為window的根控制器
UIViewController *controller = [[UIViewController alloc] init];
testWindow.rootViewController = controller;
//3. 顯示窗口
[testWindow makeKeyAndVisible];
這里,需要注意一下:
window的frame決定了這個窗口大小互婿,所以需要進(jìn)行設(shè)置
新的控制器之所以能正常顯示捣郊,是因為window強(qiáng)持有它,window能正常運(yùn)行慈参,則是因為我們用了一個暫時不會銷毀的對象強(qiáng)持有window(當(dāng)然呛牲,直接用一個靜態(tài)變量持有也可以,本質(zhì)上是一樣的)驮配。
如果window看不見娘扩,可以試試修改以下其windowLevel屬性。高等級的window一定會顯示在低等級的window上面壮锻,同等級的window,后makeKeyWindow的window就會顯示在上面琐旁。
無論是通過代碼,storyboard或xib初始化一個控制器來顯示,都是以上三步猜绣,只是創(chuàng)建控制器的方法有所區(qū)別罷了灰殴,這里不做討論了。
6.如何銷毀一個UIWindow
前面已經(jīng)說過途事,對于一個UIWindow對象验懊,之所以顯示,是因為有一個對象強(qiáng)持有它尸变,要銷毀一個window义图,只需要將這個強(qiáng)持有去掉即可。但是,這種持有去掉之后召烂,可能window可能不會立即消失碱工,所以,為了確保能夠立即將其不展現(xiàn),最好按以下步驟:
將window的hidden屬性置為YES,將持有該window的那個對象對window的持有去掉
代碼如下:
self.testWindow.hidden = YES;
self.testWindow = nil;
假如當(dāng)前這個window是keyWindow怕篷,這個window被銷毀之后历筝,系統(tǒng)會自動將上一個keyWindow設(shè)置為keyWindow,不需要我們?nèi)ス芾砝任健:唵握f就是假如以 A->B->C 這個順序變?yōu)閗eyWindow之后梳猪,C銷毀了,B會自動變?yōu)閗eyWindow蒸痹。需要注意的是春弥,不要去調(diào)用resignKeyWindow方法,該方法是用于子類重寫的叠荠,手動調(diào)用之后匿沛,結(jié)果也是未知的。
7.我們什么時候需要自己創(chuàng)建一個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ī)的主屏幕什么時候會有這種需求呢逃呼?我覺得,如果我們需要個一個控件者娱,需要獨(dú)立于其他的view,并懸浮于應(yīng)用程序中的時候抡笼,也許就需要用到UIWindow了,這里所謂的懸浮黄鳍,不過就是windowLevel比較高罷了蔫缸。
公司工程里所集成的測試控件Bugtags就是利用UIWindow實(shí)現(xiàn)的,可以懸浮在任意頁面际起,主要用于測試人員提bug,直接手機(jī)上提bug吐葱。當(dāng)然提bug這件事和本文關(guān)系不大街望,在此只是想表明這種情況就可以用UIWindow。
8.關(guān)于懸浮球
對于這個可拉拽的懸浮球弟跑,我也比較好奇灾前,所以自己著手實(shí)現(xiàn)了一下,原理也挺簡單孟辑。
創(chuàng)建一個按鈕大小的window并顯示
將其windowLevel設(shè)置得較高
在按鈕上添加拖拽手勢哎甲,隨著手勢移動,并添加一些邊界控制
那就有人問了饲嗽,這個東西有什么用炭玫?
因為公司的工程里確實(shí)沒有什么需要需要用到這個東西,但是我后來發(fā)現(xiàn)這個東西還是有那么一點(diǎn)用??貌虾。不過不是用在正式代碼之中吞加,而是開發(fā)測試階段。
做個一鍵登陸功能(公司的項目開發(fā)需要頻繁換號,輸密碼太麻煩)
如果不用換賬號衔憨,直接寫死一個賬號叶圃,點(diǎn)擊懸浮球直接登錄
如果需要頻繁換賬號的,可以把登錄過的賬號都記錄下來践图,寫到NSUserDefaults等地方掺冠,以后每次需要登陸時,點(diǎn)擊浮球码党,出來一個列表德崭,選其中一個登陸