UIWindow 教學(swift 3, iOS)

Jerry Wang
12 min readJun 6, 2017

--

在iPhone中,任一個App畫面,至少都有一個UIWindow,相信所有iOS開發者都知道。

但你知道,status bar、keyboard、Alert Controller、使用App中突然接到來電的畫面,甚至是許多人常用的Assistive Touch(俗稱的小白球),也都是一個UIWindow嗎?

本篇教學,將解說什麼是UIWindow?UIWindow與UIView的差異?最後,將實作一個屬於你自己的Assistive Touch。

推薦閱讀:(還是老話一句,看官方文件最清楚!)

UIWindow繼承自UIView,在談論UIWindow前,先簡略介紹一下UIView。

UIView主要功能為:(1)在view hierarchy中管理自己的subView(2)layout(3)畫面繪製(4)動畫(5)事件處理

而UIWindow主要功能為:(1)包含了App的所有畫面(2)事件處理(3)結合view controller,處理螢幕方向變化時的情況。

以上有點難懂,ok。

簡單來說,UIWindow就是一個空白的容器,上面沒有任何內容。

UIWindow要裝的就是我們想顯示出的畫面,而這些畫面就是從View Controller來。所有眼睛看到的畫面變換,幾乎都是VC內或VC間,各個元件的改變,而這些VC都裝在同一個UIWindow中,VC再將畫面指派給UIWindow顯示出來。

在iOS任一App中,系統預設會提供一個window,這個window佔據了整個螢幕。除非使用了外接螢幕,此時才會新增另一個window。

在AppDelegate中,系統預設提供的window,即上圖。

上面提到,App看到的畫面皆是透過VC指定給window。在UIWindow中,rootViewController屬性負責的就是我們看到的最初畫面,將我們想要作為最初畫面的VC設定為rootViewController即可。(View Controller、Navigation Controller、TabBar Controller、Split View Controller都可以)

關於rootViewController可參考下面官方文件,在此就不多加描述

但我們從未用程式碼設定過rootViewController,為什麼也會看到畫面呢?

重點就在將Is Initial…..打勾,系統就會將這個VC設定為rootViewController。

如上所提,一般來說開發者很少接觸到UIWindow,畢竟系統都處理好了,除非要處理以下情況:

(1)將座標點從某個window轉到另外一個window(發生在畫面上有大於一個window時),或者將座標點從某個window轉到view上,反之亦然。

(2)要接收UIWindow相關的Notification時。所有的window出現或消失時,會產生Notification,是否變為keyWindow時也會產生Notification。(最常用到的是監控鍵盤彈出或收起的Notification,別忘了鍵盤也是UIWindow!)

那何謂keyWindow?官方的解釋為:

Key window — that is, which window is currently receiving keyboard events and other non touch-related events. Whereas touch events are delivered to the window in which the touch occurred, events that do not have an associated coordinate value are delivered to the key window of your application. Only one window at a time may be key.

keyWindow是接收鍵盤事件及其他非觸摸事件的,而觸摸事件是由發生的UIWindow所接收。

一般來說,keyWindow即為系統預設的window。要注意的是,在同一時間永遠只會有一個keyWindow。

Most of the time, the app window becomes the key window. Because iOS uses separate windows to display alert views and input accessory views, these windows can also become key. For example, when an alert or input accessory view has a text field in which the user is currently typing, the window that contains the input view is key.

關於Touch Event Handling的教學可參考下文:

在文章的最初,提到了status bar、keyboard、Alert Controller、Assistive Touch都是UIWindow,各位是否有想過,他們互相之間的關係是怎樣?若在同一個位置時會疊在一起嗎?哪個在上面?哪個在下面呢?

這時就是要透過windowLevel來處理了。windowLevel控制的是在Z方向,各個UIWindow的相對位置,windowLevel預設是UIWindowLevelNormal,數值為0,在最底層,數字越大的話,UIWindow的相對位置越靠近使用者,也就是不會被其他UIWindow擋住。

UIWindowLevelAlert為2000、UIWindowLevelStatusBar為1000,鍵盤的windowLevel在iOS 9後已改為最高:10的7次方,任何UIWindow都擋不住鍵盤了。(對於UIWindow的windowLevel,要直接賦予值或使用UIWindowLevelNormal、UIWindowLevelAlert、UIWindowLevelStatusBar皆可)

UIWindow到這邊大概介紹完畢,有興趣的請務必研究官方文件。

接下來就開始實作屬於你自己的Assistive Touch了。

最終畫面如下:

遮住status bar
遮住alert view
即使換到下一個VC,一樣顯示在畫面上

實作Assistive Touch的方法其實很簡單,創立UIWindow subclass,在VC中初始化UIWindow subclass即可。

上方程式碼,建立了我們自己的customUIWindow,其中的image ball即Assistive Touch的圖案。在第9行加上一個panGesture,模擬Assistive Touch可以隨著我們的手指拖曳。

在第7行,我們將customUIWindow的UIWindowLevelAlert + 1,因此Assistive Touch可蓋住status bar及alert view。(可隨意修改windowLevel進行實驗)

在View Controller中,viewDidLoad內初始化customUIWindow,命名為ballWindow,並在viewDidAppear中將ballWindow設為可見。(p.s. 1 UIWindow預設皆為不可見,一定要改成可見。 p.s.2 改為可見或設為keyWindow,一定要在viewDidLoad以外的位置,否則會發生錯誤。)

在UIWindow中,若想將UIWindow設為keyWindow並可見,則要使用UIWindow的makeKeyAndVisible函數。

在此因我們的Assistive Touch功能簡單,只有拖拉,不需將其設為keyWindow。故只呼叫isHidden,設為false即可。(keyWindow是接收鍵盤事件及其他非觸摸事件的,與我們Assistive Touch的功能並不符合。)

在第12行中,我們註冊了Notification,用來監控每個window出現時的情況。

在上方程式碼中,第3行內,Notification的object為UIWindow。第4行透過UIApplication,可獲取目前全部的UIWindow。

接著將我們需要的資訊印出來

上圖為一開啟App時,Assistive Touch的資訊,可看到其windowLevel為2001,正是我們設定的UIWindowLevelAlert + 1。此時的window總數為2個,即預設的window和Assistive Touch(CustomUIWindow)

上圖為跳出Alert Controller時,系統給定的Alert Controller內部的windowLevel為10。此時的window總數為3個,即預設的window、Assistive Touch、Alert View(UITextEffectsWindow)。

值得注意的是,在Alert Controller消失後,其UITextEffectsWindow並不會消失,仍會留在UIWindow階層上,因此後續再點下alert按鈕,不會再有Become Visible資訊出現。同樣的,因為UITextEffectsWindow不會消失,故下圖鍵盤跳出後,window目前總數為4個就可以解釋了。

上圖為跳出鍵盤時,鍵盤的windowLevel達到最大,為10的7次方,若故意設定windowLevel大於10的7次方也沒用,最高就是10的7次方,一樣蓋不住鍵盤。

綜合以上所述及實驗可知,不論跳出鍵盤或跳出Alert,keywindow仍為原先系統預設的window,因此不會觸發Become key的通知。(這部分可自己做實驗看看)

也可參考以下文章:

以上就是UIWindow的教學及Assistive Touch的實作教學,未來若要客製化Alert View時,一樣可以建立自己的UIWindow,指定windowLevel為UIWindowLevelAlert,創立一個VC,將想做的畫面刻在上面,即可客製化了。

最終專案:

--

--