iOS懸浮球的實(shí)現(xiàn)

簡單的懸浮小球demo下載地址

Untitled.gif

帶測試小項目的demo下載

Untitled.gif

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)擊浮球码党,出來一個列表德崭,選其中一個登陸

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市闽瓢,隨后出現(xiàn)的幾起案子接癌,更是在濱河造成了極大的恐慌,老刑警劉巖扣讼,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缺猛,死亡現(xiàn)場離奇詭異,居然都是意外死亡椭符,警方通過查閱死者的電腦和手機(jī)荔燎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來销钝,“玉大人有咨,你說我怎么就攤上這事≌艚。” “怎么了座享?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長似忧。 經(jīng)常有香客問我渣叛,道長,這世上最難降的妖魔是什么盯捌? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任淳衙,我火速辦了婚禮,結(jié)果婚禮上饺著,老公的妹妹穿的比我還像新娘箫攀。我一直安慰自己,他們只是感情好幼衰,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布靴跛。 她就那樣靜靜地躺著,像睡著了一般塑顺。 火紅的嫁衣襯著肌膚如雪汤求。 梳的紋絲不亂的頭發(fā)上俏险,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機(jī)與錄音扬绪,去河邊找鬼竖独。 笑死,一個胖子當(dāng)著我的面吹牛挤牛,可吹牛的內(nèi)容都是我干的莹痢。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼墓赴,長吁一口氣:“原來是場噩夢啊……” “哼竞膳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起诫硕,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤坦辟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后章办,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锉走,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年藕届,在試婚紗的時候發(fā)現(xiàn)自己被綠了挪蹭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡休偶,死狀恐怖梁厉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情踏兜,我是刑警寧澤词顾,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站碱妆,受9級特大地震影響计技,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜山橄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望舍悯。 院中可真熱鬧航棱,春花似錦、人聲如沸萌衬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽秕豫。三九已至朴艰,卻和暖如春观蓄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背祠墅。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工侮穿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人毁嗦。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓亲茅,卻偏偏與公主長得像,于是被迫代替她去往敵國和親狗准。 傳聞我的和親對象是個殘疾皇子克锣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345

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