聊聊ViewController
本來想重新複習一下ViewController的載入過程的,後來發現要寫的東西還是挺多了,看了很多資料,寫了很多程式碼,當我發現控制器的轉場動畫的時候,感覺這坑太大了,還是以後專門抽個時間單獨地去看看檢視轉場的動畫吧~
層次圖
首先是ViewController的層次結構圖:
當然這個是最基本的層次圖,在實際的運用中可以回涉及到大量的viewController
之間的互動,層級結構等。
載入順序
在使用ViewController的時候,有一個常見的問題,就是它的方法載入的順序,在這裡主要分兩種載入順序,一個是使用code建立的ViewController,一個是IB建立的ViewController,前者順序如下:
- convenience init
- super.init(nibName:, bundle:)
- loadView
- viewDidLoad
- viewWillAppear
- viewWillLayoutSubviews
- viewDidLayoutSubviews
- viewDidAppear
使用IB建立的ViewController,載入順序如下(viewWillLayoutSubviews可能呼叫多次)
- init coder
- awakeFromNib
- loadView
- viewDidLoad
- viewWillAppear
- viewWillLayoutSubviews
- viewDidLayoutSubviews
- viewDidAppear
針對這個不同的載入順序,有幾個點要拿出來講一講:
1. 為什麼程式碼初始化需要呼叫父類的init(nibName:, bundle:)?
The designated initializer. If you subclass UIViewController, you must call the super implementation of this method, even if you aren't using a NIB. (As a convenience, the default init method will do this for you, and specify nil for both of this methods arguments.) 在Apple的註釋中寫道,如果你建立UIViewController的話(即使沒有使用NIB),一定要在自定義的初始化方法中寫上父類的實現即:
init() {
super.init(nibName: nil, bundle: nil)
}
複製程式碼
即使沒有convenient init()方法,預設的init方法也會為其呼叫父類的init(nibName: nil, bundle: nil)方法
2. 兩種建立ViewController例項載入的區別在哪裡?
使用IB建立的viewController手動呼叫init(nibName: nil, bundle: nil)時,當然會呼叫這個方法,如果沒有呢?即1. 用IB自動跳轉,2. 或者使用程式碼用segue
跳轉,3. 或者使用程式碼的instantiateViewController(withIdentifier:)
這三種方式建立,都不會呼叫init(nibName:, bundle:)
方法,而是呼叫init(coder:)
方法
使用IB建立的ViewController一定會呼叫呼叫
init(coder:)
方法
3. loadView()
VS viewDidLoad()
在View Controller Programming Guide for iOS)中:
If you prefer to create views programmatically, instead of using a storyboard, you do so by overriding your view controller’s loadView method. Your implementation of this method should do the following:
- Create a root view object. …
- Create additional subviews and add them to the root view. For each view, you should:
- Create and initialize the view.
- Add the view to a parent view using the addSubview: method.
- If you are using auto layout, assign sufficient constraints to each of the views you just created to control the position and size of your views.
- Assign the root view to the view property of your view controller.
如果使用程式碼而不是storyboard建立Views的話,那麼Apple建議過載VC的loadView(),在過載方法體內部需要新增什麼呢?
- 建立根檢視物件
- 建立子檢視物件,且新增子檢視物件到根檢視物件上
- 新增autolayout設定約束(如果需要的話)
- 將root view賦值給vc的view屬性
上面是針對純程式碼建立views,如果是使用IB建立呢?
If you cannot define your views in a storyboard or a nib file, override the loadView method to manually instantiate a view hierarchy and assign it to the view property. … You can override [the loadView] method in order to create your views manually. If you choose to do so, assign the root view of your view hierarchy to the view property. … If you want to perform any additional initialization of your views, do so in the viewDidLoad method.
也就是說Apple,其實是推薦在loadView中對檢視層次結構中進行設定的,而不是在ViewDidLoad()中,但是我們通常是在ViewDidLoad()中對views進行設定的,但是這也是安全的,因為loadView()呼叫結束之後,就立馬呼叫了ViewDidLoad(),雖然不合蘋果規範,但是也是可行的!
那麼在loadView的方法中發生了什麼呢?
-
如果關聯了StoryBord或者XIB
- 從storyboard / xib中載入檢視
- 將root view賦值給viewController的view屬性
-
如果沒關聯StoryBord或者XIB
- 建立一個UIView例項有一個預設的frame和autoResizingMask
- 將例項賦值給viewController的view屬性
-
根據StoryBord或者XIB中的 layout guides設定約束
-
呼叫viewDidLoad()
小總結
- 建議還是在loadView()中設定檢視,不設定也行,你得明白檢視初始化的過程中發生了什麼
- 最好:在loadView中對viewController的view屬性進行set;在viewDidLoad中只去get
- 以上只適用於UIViewController的子類,如果是子類的子類,仍然在viewdidload中進行檢視的設定
4. 為什麼layoutSubviews要呼叫多次?
如果你在storyboard上的控制器上加了一個控制元件,然後將storyboard上的這個控制器和一個自定義class關聯起來,viewWillLayoutSubviews/viewDidLayoutSubviews
會呼叫兩次,為什麼?
其一: 檢視本身的根檢視需要佈局
其二: storyboard上新增的控制元件即根檢視上的控制元件需要佈局
所以,當我將storyboard上的控制元件刪除之後,整個啟動過程中viewWillLayoutSubviews/viewDidLayoutSubviews
只會呼叫一次
那麼有哪些因素會導致layoutview要呼叫呢?
- viewController的view屬性的bounds發生了改變
- 新增子檢視會發生呼叫此方法
- 旋轉螢幕會呼叫此方法
Segue的行為
在使用StoryBoard建立檢視的時候,segue的行為會發生什麼呢?
- shouldPerformSegue 方法可以阻止segue的行為
- prepareForSegue:方法可以將資料從源檢視控制器傳遞到目標控制器