iOS UIWindow及懸浮球

UIWindow

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è)步驟:

  1. 創(chuàng)建一個(gè)window對象认轨,并用一個(gè)對象強(qiáng)持有
  2. 創(chuàng)建一個(gè)控制器绅络,賦值為window的根控制器
  3. 顯示窗口

代碼如下:

//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),最好按以下步驟:

  1. 將window的hidden屬性置為YES
  2. 將持有該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愈魏,謝謝觅玻。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末艇棕,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子串塑,更是在濱河造成了極大的恐慌沼琉,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件桩匪,死亡現(xiàn)場離奇詭異打瘪,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)傻昙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門闺骚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人妆档,你說我怎么就攤上這事僻爽。” “怎么了贾惦?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵胸梆,是天一觀的道長。 經(jīng)常有香客問我须板,道長碰镜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任习瑰,我火速辦了婚禮绪颖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘甜奄。我一直安慰自己柠横,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布课兄。 她就那樣靜靜地躺著牍氛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪第喳。 梳的紋絲不亂的頭發(fā)上糜俗,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機(jī)與錄音曲饱,去河邊找鬼。 笑死珠月,一個(gè)胖子當(dāng)著我的面吹牛扩淀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播啤挎,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼驻谆,長吁一口氣:“原來是場噩夢啊……” “哼卵凑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起胜臊,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤勺卢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后象对,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體黑忱,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年勒魔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了甫煞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡冠绢,死狀恐怖抚吠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情弟胀,我是刑警寧澤楷力,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站孵户,受9級特大地震影響弥雹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜延届,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一剪勿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧方庭,春花似錦厕吉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至龄减,卻和暖如春项钮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背希停。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工烁巫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宠能。 一個(gè)月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓亚隙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親违崇。 傳聞我的和親對象是個(gè)殘疾皇子阿弃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容