- 普段、iOSアプリを開発していて、Viewの描画サイクルをなんとなくで理解していた節があったため、ここにメモを残す。
Viewの描画サイクルを理解する
始めに
- ユーザーがアプリに対して何らかの処理を実行すると、そのイベントがイベントキューに追加され、アプリ内のオブジェクトにディスパッチされます。そして、その処理終了後にメインランループに処理を戻した上で、ビューのレイアウトと描画を制御するアップデートサイクルが走ります。
アップデートサイクル
- 一度制御がメインループに戻ると、システムはConstraintsを元に、Viewのインスタンスを生成します。
- その際に、なんらかのビューが次の更新サイクルで変更が必要であると、記述されていた場合に、システムは一度その更新用のレイアウトパスの前にConstraintsをかけます。
遅延されたレイアウトパス
- ちなみに、ここでいう「遅延されたレイアウトパス」とは、なんらかのビューの変更を次の更新時にするにあたってのConstraintsのレイアウトパスのことを指します。
- つまり、ViewController生成時でなく、後々サイズを大きくしたかったり、動きをつけたかったりするオブジェクトに必要なレイアウトパスのことです。また、UIComponentに含まれていないものを
draw(_ rect: CGRect)
などを通して表示したい時になります。 - まず、Constraintsは基本的にViewController生成時に追加されます。(おそらくviewDidLoad時)
- しかし、その生成時には動的なレイアウトの変更はシステムによって、すぐに処理されるわけではなく、そのまま「遅延されたレイアウトパス」としてシステム内にとどまる。
- なので、動的にただレイアウトを変えようとしてもこのままでは何も変わらない。
解決策
-
setNeedsLayout()
を呼ぶことで、この問題に対処できる。 - ドキュメントによると、実際に「遅延されたレイアウトパス」には以下の2つのパスが含まれるらしいです。
- update パスは、必要に応じて制約を更新します。これは、すべてのビューコントローラで updateViewConstraints メソッドを呼び出し、すべてのビューで updateConstraints メソッドを呼び出します。
- レイアウトパスは、必要に応じて、ビューのフレームを再配置します。これは、すべてのViewControllerで viewWillLayoutSubviews を呼び出し、各ビューで layoutSubviews を呼び出します。
Constraintsのアップデートの仕組み
- Viewが更新される際は、
func draw(_ rect: CGRect)
という関数が呼ばれる。
ビューの再描画のために必要な関数
-
setNeedsDisplay()
とは-
setNeedsDisplay()
はあるViewに対して再描画を強制させる関数です。
-
-
layoutIfNeeded()
- これが呼ばれた時にただちにレイアウトのアップデートをすることができる。animationのConstraintsをかける際に、よく使われる。
-
layout Subview
- これは
overriding func
であり、Viewの生成時に呼ぶことができる。つまり、ここにおいて、Viewのcorner radius
などを呼ぶのが適している。 - ViewControllerにおける
viewDidLayoutSubviews()
とやっていることは同じ。
- これは
まとめ
- 基本的にViewが変わった際に、オートレイアウトがシステムによって計算されることによってレイアウトは自動的に変更される。
- しかし、ために自らシステムに対してレイアウトをアップデートし、計算をさせる処理を走らせる必要がある。