聊聊ViewController

Jani發表於2018-04-03

聊聊ViewController

本來想重新複習一下ViewController的載入過程的,後來發現要寫的東西還是挺多了,看了很多資料,寫了很多程式碼,當我發現控制器的轉場動畫的時候,感覺這坑太大了,還是以後專門抽個時間單獨地去看看檢視轉場的動畫吧~

層次圖

首先是ViewController的層次結構圖:

image
當然這個是最基本的層次圖,在實際的運用中可以回涉及到大量的viewController之間的互動,層級結構等。

載入順序

在使用ViewController的時候,有一個常見的問題,就是它的方法載入的順序,在這裡主要分兩種載入順序,一個是使用code建立的ViewController,一個是IB建立的ViewController,前者順序如下:

  1. convenience init
  2. super.init(nibName:, bundle:)
  3. loadView
  4. viewDidLoad
  5. viewWillAppear
  6. viewWillLayoutSubviews
  7. viewDidLayoutSubviews
  8. viewDidAppear

使用IB建立的ViewController,載入順序如下(viewWillLayoutSubviews可能呼叫多次)

  1. init coder
  2. awakeFromNib
  3. loadView
  4. viewDidLoad
  5. viewWillAppear
  6. viewWillLayoutSubviews
  7. viewDidLayoutSubviews
  8. 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:

  1. Create a root view object. …
  2. 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.
  3. 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.
  4. Assign the root view to the view property of your view controller.

如果使用程式碼而不是storyboard建立Views的話,那麼Apple建議過載VC的loadView(),在過載方法體內部需要新增什麼呢?

  1. 建立根檢視物件
  2. 建立子檢視物件,且新增子檢視物件到根檢視物件上
  3. 新增autolayout設定約束(如果需要的話)
  4. 將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的方法中發生了什麼呢?

  1. 如果關聯了StoryBord或者XIB

    • 從storyboard / xib中載入檢視
    • 將root view賦值給viewController的view屬性
  2. 如果沒關聯StoryBord或者XIB

    • 建立一個UIView例項有一個預設的frame和autoResizingMask
    • 將例項賦值給viewController的view屬性
  3. 根據StoryBord或者XIB中的 layout guides設定約束

  4. 呼叫viewDidLoad()

小總結

  1. 建議還是在loadView()中設定檢視,不設定也行,你得明白檢視初始化的過程中發生了什麼
  2. 最好:在loadView中對viewController的view屬性進行set;在viewDidLoad中只去get
  3. 以上只適用於UIViewController的子類,如果是子類的子類,仍然在viewdidload中進行檢視的設定

4. 為什麼layoutSubviews要呼叫多次?

如果你在storyboard上的控制器上加了一個控制元件,然後將storyboard上的這個控制器和一個自定義class關聯起來,viewWillLayoutSubviews/viewDidLayoutSubviews會呼叫兩次,為什麼? 其一: 檢視本身的根檢視需要佈局 其二: storyboard上新增的控制元件即根檢視上的控制元件需要佈局 所以,當我將storyboard上的控制元件刪除之後,整個啟動過程中viewWillLayoutSubviews/viewDidLayoutSubviews只會呼叫一次

那麼有哪些因素會導致layoutview要呼叫呢?

  1. viewController的view屬性的bounds發生了改變
  2. 新增子檢視會發生呼叫此方法
  3. 旋轉螢幕會呼叫此方法

Segue的行為

在使用StoryBoard建立檢視的時候,segue的行為會發生什麼呢?

segue

  • shouldPerformSegue 方法可以阻止segue的行為
  • prepareForSegue:方法可以將資料從源檢視控制器傳遞到目標控制器

參考文章

View Controller Programming Guide for iOS 中文版

相關文章