レイアウトサイクル

StoryboardのAutolayoutで設定した値を編集しても、それが画面に反映されない。
そんなことが多々あります。
原因はおおよそ、描画の処理が完了した後にAutolayoutの編集を行うなど、タイミングによるものがあります。

なので、一度そのタイミングについて勉強していきましょう。

レイアウトサイクルは大きく分けて以下のように分けられています。

  1. 制約(Constraints)
  2. レイアウト(Layout)
  3. 描画(Draw)

1. 制約(Constraints)

このステップでAutoLayoutの制約を更新します。
marginの設定やalignの設定などです。
子ビューから親ビューの順番でレイアウトが更新されます。

updateConstraints

このメソッドは制約を更新するときに呼ばれます。
以下の点で注意が必要です。

  • スーパークラスのメソッドは最後に呼ぶ
  • UIViewのupdateConstraintsメソッドをコードで呼んではいけない
  • setNeedsLayout、layoutIfNeeded、setNeedsDisplayメソッドを呼んではいけない

システム側がこのメソッドを勝手に呼び出してくれます。
システムに制約を更新が必要であることを伝えるためにsetNeedsUpdateConstraintsというメソッドがあります。
でも、このメソッドはあくまで更新であることを伝えるだけで、更新はされません。あくまで更新されるタイミングはシステムが決めています。
システムが制約の更新を行う際、updateConstraintsIfNeededメソッドを呼びます。これはコードで書いて呼んでも問題ないです。このupdateConstraintsIfNeededが呼ばれる前にupdateConstraintsIfNeededメソッドを呼んだビューとそのサブビューは更新の対象となります。呼ばれないものに関しては、システム側でupdateConstraintsメソッドが呼ばれます。

IntrinsicContentSize

Viewが持っている独自のサイズです。
UILabelの場合、文字列が収まるサイズがIntrinsicContentSizeになります。

updateViewConstraints

ViewControllerクラスのメソッドです。
このメソッドはupdateViewConstraintsはViewControllerのself.viewのupdateConstraintsの代わりです。
コードは以下のように書きます。

override func updateViewConstraints() {
    super.updateViewConstraints()
    //ここにself.viewのSubviewの制約を更新するコードを書く
}

ビューのsetNeedsUpdateConstraintsメソッドとupdateConstraintsIfNeededメソッドを呼ばれた直後にレイアウトの更新をするupdateViewConstraintsメソッドが呼ばれます。
コードではこんな感じです。

class FirstViewController: UIViewController {
    func methodA() {
        self.view.setNeedsUpdateConstraints()
        self.view.updateConstraintsIfNeeded()
        // ここでself.updateViewConstraintsメソッドが呼ばれる
    }
}

2. レイアウト(Layout)

1. の制約をもとにレイアウトを実行します。ここでViewのcenterとboundsを決定します。
親ビューから子ビューの順番でレイアウトが更新されます。

layoutSubviews

このメソッドはシステム側が呼ぶので直接呼んではいけないメソッドです。
オーバーライドするときは以下のようにスーパークラスのメソッドを呼んであげる必要があります。

override func layoutSubviews() {
    super.layoutSubviews()
    // ここに処理を書く
}

layoutIfNeededを呼んだViewとそのSubviewのうち、setNeedsLayoutを呼んだViewに対して、layoutSubviewsメソッドが呼ばれます。
以下のような感じです。

class FirstViewController: UIViewController {
    func methodB() {
        self.view.setNeedsUpdateConstraints()
        self.view.updateConstraintsIfNeeded()
        // ここでself.updateLayoutsメソッドが呼ばれる
    }
}

viewWillLayoutSubviewsとviewDidLayoutSubviews

3. 描画(Draw)

2. のレイアウト後にUIViewのdrawRect(rect: CGRect)が呼ばれます。
ここではCore Graphicsを使って描画します。
Core Graphicsで描画が必要なアプリはあまり多くないらしいです。
ここでは特に書きません。

そして、レイアウトサイクルは1. 制約、2. レイアウト、3. 描画の順番で行われます。
1. 制約と2. レイアウトにはUIViewとUIViewControllerの両方にメソッドが用意されています。

今回参考にさせていただいたページは以下になります。
iOSエンジニア必見!!iOSのレイアウトで押さえておきたいこと【総集編】