Auto Layout System’s Life Cycle

LukeWu
Aiworks
Published in
9 min readMay 6, 2021

Apple 為了解決 iOS 多種手機尺寸的頁面設計問題,早在多年前就已經開發了一套佈局工具 : Auto Layout System。不過有一些 UI 設計,需要 Auto Layout 與其他設定搭配的時候,比如說單純調整 layer 的大小,或是切圓角,就會有一些問題,這是使用 Auto Layout 時,最常發生異常的狀況,之所以寫這邊文章,就是希望協助新手對 Auto Layout 的運作機制,有更充分的理解!

首先,讓我們透過建立一個有紅色圓形的畫面開始,產生圓形的條件有:

  1. 必須是一個長寬相同的 View,並把它加到畫面上。
  2. UIView 的 layer.cornerRadius 值剛好要跟 view 的 frame 長寬的一半
  3. UIView 的 clipsToBounds property 必須是 true

透過 Frame 的計算來實現

我們先產生一個 testView,並指定它的 Frame,最後根據 testViewWidth 來設定 cornerRadius 的 value。

透過以下的程式碼,我們可以在畫面上,產生一個紅色的小圓形。

透過 Auto Layout 來實現

我們一樣先產生一個 testView,並指定他的 Auto Layout Constraint,最後一樣根據 testViewwidth 來設定 cornerRadius 的 value。這一次畫面不會出現圓形,反而出現了一個正方形的 View。

釐清問題

看來我們遇上了 Bug,讓我們回過頭來看剛剛產生圓形的三個條件:

  1. 必須是一個長寬相同的 View,並把它加到畫面上。
  2. UIView 的 layer.cornerRadius 值剛好要跟 View 的長寬的一半
  3. UIView 的 clipsToBounds property 必須是 true

1、3 項在前後兩個做法,都沒有做到修改,眼前比較有可能出現問題的,就是第 2 點,讓我們來查一下這一行:

testView.layer.cornerRadius = testView.frame.width / 2

如果你將 testView.frame.width / 2 印出來看:在 Frame 的寫法,他會是 50,但在 Auto Layout 的寫法,卻會是 0,也就是 testView.frame.width 的數值是 0。

看來問題就出在這了:

我們明明有下 Auto Layout Constraints,為什麼寬不是 100 ?

為什麼 Auto Layout 不 Work ?

看到這個結果,我們第一個想法通常是:為什麼 Auto Layout 不 work ?

但讓我們靜下心來看一下,最後我們的 testView 其實有出現在畫面上,長寬跟位置基本上跟我們 Auto Layout 的設定差不多,感覺 Auto Layout 是有 Work 啊,為什麼我會拿到一個 Width 為 0 的 Frame?

事實上,Auto Layout 的確有 Work,你也確實拿到了一個 Width 是 0 的 Frame。

那到底是誰在搞鬼?

Auto Layout 的機制

事實上當我們在設定跟規劃 Auto Layout 的時候,我們是在描述一些位置跟大小的關係,比如說 testView 的 Leading 要跟 view 的 Leading 距離多少, testView的 top 距離 view 的 top 要多少;我們並沒有真正的指定 x, y, width, height 任何一個屬性。

這些位置跟大小的關係(Constraints),會在適當的時候被 Auto Layout System 計算,並轉換成 Frame,套用到每一個有使用 Constraint 的 View 上面。

所以上面產生正方形紅色 View 的例子,我們讀取到 frame.width 為 0,是因為我們在 Auto Layout System 還沒作用前就去存取 frame,拿到的當然就不會是最終的結果。

Auto Layout System 什麼時候會計算完畢

那到底我們什麼時候去 Access View Frame 才會拿到計算完的結果?iOS 在 UIViewController Class 裡面留了兩個跟 Auto Layout System 有關的 Method:

在整個 Auto Layout System 開始運作之前,iOS System 會先執行 viewWillLayoutSubviews,如果在 auto layout 開始之前想做一些設定,可以 override 這個 method,原生的 implementation 內沒有做任何事情。在這個 method 裡面,Auto Layout System 是即將開始計算,也就是在這邊拿到的 Frame,並不是最終的結果

當 Auto Layout System 完成了計算,並把結果套到每一個 View 上面後,iOS 系統會來執行這個 method。也就是在這個 method 裡面,我們去 Access View 的 Frame,會拿到計算完的結果。

如果你希望藉由 Auto Layout System 算完的結果,來做一些運算或是設計,就可以把相關的程式碼寫在 viewDidLayoutSubviews method 裡面。

另外還有一個 View Life Cycle 的 method 你也可以拿到 Auto Layout 計算完的結果:

在整個 UIViewController 所管理的 View 已經出現在畫面上之後,Auto Layout 一定也算好了,所以在這個 Method 裡面,我們也可以拿到 Auto Layout 計算完的結果。

原來一切都是時機不對

viewDidLayoutSubviews 與 viewDidAppear 的取捨

這兩個 method 都可以拿到 Auto Layout 計算完的結果,那我們要把程式碼寫在哪一個好?讓我們先從這兩個 Method 的特性說起:

  • viewDidLayoutSubviews :
  1. 執行次數:

只要畫面有更新,Layout 有變化,就有可能會被執行;一個畫面從無到有的過程,基本上這個 method 通常會被系統執行很多次

影響:如果在這裡寫了一些運算時間比較長的程式碼,基於 method 會被執行很多次,要有心裡預期這可能會影響效能。

2. 畫面出現與否:

在一個畫面從無到有出現的過程,viewDidLayoutSubviews 被執行的時候,view 基本上都尚未出現在螢幕上,也就是如果你在這個 method 去調整畫面的設定,使用者都不會發現(因為畫面還沒出現)。

但必須要注意的是,畫面出現之後,只要畫面需要更新,則這個 Method 會再次被呼叫

ex: 使用者旋轉手機導致畫面旋轉。

  • viewDidAppear:
  1. 執行次數

一個畫面從無到有的過程,基本上這個 method 通常只會被執行一次。但在不同 Scene 之間來回,則這個 Method 一樣會再次被執行。

ex:切換 Tab, Navigate 到下一個 Scene 再 Back 回來。

2. 畫面出現與否:

基本上這個 Method 被執行的時候,畫面已經出現在手機上,使用者也可以看得到畫面。

影響:在這個 Method 內去調整畫面,使用者有機會觀察到畫面的變化

以一般的應用來說,我們通常會將畫面的調整寫在 viewDidLayoutSubviews 裡面,避免讓使用者看到畫面閃爍。但有時候也可以透過一些手法,比如果說一開始將畫面藏起來,viewDidAppear 再去調整畫面,搭配淡入的動畫,也可以達到同樣的效果。

相關的 Method

In UIView Class:

當 Auto Layout System 安排好這個 Method 的 Receiver 底下的 Subviews 時,系統會來執行這個 Method,我們在裡面可以拿到 SubViews 計算完的 Frame。

強制 Auto Layout System 立刻根據 Constraints 更新 Receiver 底下 SubViews 的 Frame。

Invalid 現有的 Constraints, 並在下一個 Layout Cycle 以新的 Constraints 更新畫面。

最後有一點值得提出來說明的是:

viewDidLayoutSubviews 被執行並不代表底下 View 的個別 SubViews 都被給予最終 Frame 了,各自的 View 底下的 SubView 還是由他們自身的 Layout Cycle 控制,也就是在 layoutSubviews 裡面才能拿到最終的大小。

Snapshot From Apple Doc

結論

使用 Auto Layout 常常讓人有已經安排好畫面的錯覺,事實上必須要在對的時機點去 Access View Frame 才能拿到正確的結果,也就是 viewDidLayoutSubviews 以及 viewDidAppear 這兩個 Method。有時候你希望他先把 Auto Layout 算完,則可以透過 UIView 的 layoutIfNeed,強迫 Auto Layout System 立刻計算。

一但了解了 Auto Layout 的機制,在一些特殊畫面控制上,就會更加得心應手,也不會再對於畫面的失控,百思不得其解囉。

--

--