
帶測試小項目的demo下載

1.UIWindow簡介
一個UIWindow對象為應(yīng)用程序的用戶界面提供了背景以及重要的事件處理行為。
UIWindow繼承自UIView,我們一般不會直接去設(shè)置其UI展現(xiàn),但它對展現(xiàn)程序中的views至關(guān)重要。每一個view,想要出現(xiàn)在屏幕上都依賴于window,但是程序中的window之間是相互獨立的。應(yīng)用程序收到事件之后會先轉(zhuǎn)發(fā)給適當(dāng)?shù)膚indow對象,從而又將事件轉(zhuǎn)發(fā)給view對象。
2.程序中有哪些UIWindow
所有view的展現(xiàn)都依賴于window,創(chuàng)建一個新的iOS工程,將其運行會執(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,其實可以使用
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è)備的物理屏幕,一般情況下,我們不需要對其進(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之間是相互獨立的,如果想要將兩個window的坐標(biāo)相互映射的時候,就需要用到以上幾個方法。
5.如何創(chuàng)建一個UIWindow并顯示
主要有以下幾個步驟:
創(chuàng)建一個window對象,并用一個對象強持有它,創(chuàng)建一個控制器,賦值為,window的根控制器顯示窗口
代碼如下:
//1. 創(chuàng)建一個window對象,并用一個對象強持有它
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強持有它,window能正常運行,則是因為我們用了一個暫時不會銷毀的對象強持有window(當(dāng)然,直接用一個靜態(tài)變量持有也可以,本質(zhì)上是一樣的)。
如果window看不見,可以試試修改以下其windowLevel屬性。高等級的window一定會顯示在低等級的window上面,同等級的window,后makeKeyWindow的window就會顯示在上面。
無論是通過代碼,storyboard或xib初始化一個控制器來顯示,都是以上三步,只是創(chuàng)建控制器的方法有所區(qū)別罷了,這里不做討論了。
6.如何銷毀一個UIWindow
前面已經(jīng)說過,對于一個UIWindow對象,之所以顯示,是因為有一個對象強持有它,要銷毀一個window,只需要將這個強持有去掉即可。但是,這種持有去掉之后,可能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)ス芾?。簡單說就是假如以 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一般用于外接的屏幕,那在我們手機的主屏幕什么時候會有這種需求呢?我覺得,如果我們需要個一個控件,需要獨立于其他的view,并懸浮于應(yīng)用程序中的時候,也許就需要用到UIWindow了,這里所謂的懸浮,不過就是windowLevel比較高罷了。
公司工程里所集成的測試控件Bugtags就是利用UIWindow實現(xiàn)的,可以懸浮在任意頁面,主要用于測試人員提bug,直接手機上提bug。當(dāng)然提bug這件事和本文關(guān)系不大,在此只是想表明這種情況就可以用UIWindow。
8.關(guān)于懸浮球
對于這個可拉拽的懸浮球,我也比較好奇,所以自己著手實現(xiàn)了一下,原理也挺簡單。
創(chuàng)建一個按鈕大小的window并顯示
將其windowLevel設(shè)置得較高
在按鈕上添加拖拽手勢,隨著手勢移動,并添加一些邊界控制
那就有人問了,這個東西有什么用?
因為公司的工程里確實沒有什么需要需要用到這個東西,但是我后來發(fā)現(xiàn)這個東西還是有那么一點用??。不過不是用在正式代碼之中,而是開發(fā)測試階段。
做個一鍵登陸功能(公司的項目開發(fā)需要頻繁換號,輸密碼太麻煩)
如果不用換賬號,直接寫死一個賬號,點擊懸浮球直接登錄
如果需要頻繁換賬號的,可以把登錄過的賬號都記錄下來,寫到NSUserDefaults等地方,以后每次需要登陸時,點擊浮球,出來一個列表,選其中一個登陸