setNeedsLayoutやlayoutSubviewsなどの役割を整理する
Table of Contents
layoutIfNeededはビュー位置すぐ分かって便利。layoutSubviewsは呼んだらいけない。setNeedsLayoutはパフォーマンス良し。
ビュー周りをコーディング中にUIが反映されない不具合に遭遇したら、
「ビューが反映されていない。とりあえず可能性を潰すためにConstraintやLayoutを更新」
って思って、Xcode上でconstraintとかlayoutって打ったら候補に上がるたくさんのメソッド。
「とりあえずそれぞれ何個か呼べば反映処理は走るだろう・・・」
なんてことないですか?
この記事では、setNeedsLayout,layoutIfNeeded,layoutSubviews,updateConstraints,setNeedsUpdateConstraints,updateConstraintsIfNeededについて調べて、詳細は省いて噛み砕いて説明します。
iOSのレイアウト更新をメソッド整理する
- 制約系
updateConstraintssetNeedsUpdateConstraintsupdateConstraintsIfNeeded
- レイアウト系
layoutSubviewssetNeedsLayoutlayoutIfNeeded
updateConstraints
システムが制約更新に呼ぶ 自分らが直接呼ばない。
使う用途は、オーバーライドして制約更新の最適化。 そもそも、これをオーバーライドしてまでパフォーマンスを求められる複雑な画面はつくらないほうがいい。
setNeedsUpdateConstraints
事前に更新予約することで複数ビュー更新時の無駄な再計算の抑制。 フラグセット処理なので処理は軽い。その場で処理はされない。
updateConstraintsIfNeeded
システムがレイアウト更新時に呼ぶ。最新の制約を得たい場合は手動で呼ぶことも可能。
setNeedsUpdateConstraintsの後にこれを呼ぶと制約更新が走る。
layoutSubviews
制約を使いサブビューのサイズと位置を決定する。
このメソッドは直接呼んではならない、代わりにsetNeedsLayout使うこと。
もし、ビューのレイアウトをすぐ欲しい場合は、layoutIfNeededを呼ぶこと。
setNeedsLayout
サブビューのレイアウトを無効化する=次回レイアウト更新時に更新を必須とする。
全てのレイアウト更新を1回の更新サイクルで済ませられるのでパフォーマンス良い。
layoutIfNeeded
サブビューが保留中ならすぐさま更新する。更新不要なら何もしない。
呼んだビューをルートとして、サブビューのレイアウト更新する。
内部では制約の変更も更新する。
setNeedsLayoutを呼んだビューで呼ぶとその場でレイアウト更新処理が走る。
制約変更してすぐにレイアウトが欲しい場合
制約を変更した直後に、影響を受けた後のビュー状態が欲しい場合は、
影響を受けるビューの親ビューのlayoutIfNeededを呼ぶ。
これにより無効化されたビューのレイアウト更新が実行されます。
レイアウト更新は内部でupdateConstraintsが呼ばれてから、layoutSubviewsが呼ばれてます。
コードで表す
あるUIViewControllerにUILabelが配置されてるとする。
UILabelは左右と上にsuperviewとConstraintを引いている。
左右のConstraintsはそれぞれ16になっている。
左右のConstraints(labelRightConstraintとlabelLeftConstraint)の値を変更。
ラベルの中身も変更する。
この状態で制約変更後のラベルの座標が欲しい場合は、layoutIfNeededを呼ぶ。
setNeedsLayoutは呼ばなくてもラベルの変更によりレイアウトが無効化(更新必須化)されている。
self.labelRightConstraint.constant = 30.0
self.labelLeftConstraint.constant = 40.0
self.label.text = "asdfasdfasdfasdf"
self.view.layoutIfNeeded()
print("ラベルX:", self.label.frame.origin.x)
コンソール
ラベルX: 40.0
親ビューを更新しないといけない
layoutIfNeededをラベルの親ビューではなく、ラベル自身で呼んでも変更されない。
self.labelRightConstraint.constant = 30.0
self.labelLeftConstraint.constant = 40.0
self.label.text = "asdfasdfasdfasdf"
self.label.layoutIfNeeded()
print("ラベルX:", self.label.frame.origin.x)
コンソール
ラベルX: 16.0
setNeedsLayoutは無効化するだけ
setNeedsLayoutを呼ぶと次回更新時に更新されるが、その場で更新はされない。
self.labelRightConstraint.constant = 30.0
self.labelLeftConstraint.constant = 40.0
self.label.text = "asdfasdfasdfasdf"
self.view.setNeedsLayout()
print("ラベルX:", self.label.frame.origin.x)
コンソール
ラベルX: 16.0
Constraint更新してもフレーム位置は変わらない
setNeedsUpdateConstraintsとupdateConstraintsIfNeededで制約更新しても
レイアウト更新はされないので、ラベルの位置は更新されない。
self.labelRightConstraint.constant = 30.0
self.labelLeftConstraint.constant = 40.0
self.label.text = "asdfasdfasdfasdf"
self.label.setNeedsUpdateConstraints()
self.label.updateConstraintsIfNeeded()
print("ラベルX:", self.label.frame.origin.x)
コンソール
ラベルX: 16.0
あまり使うケース少ない
setNeedsLayoutとsetNeedsUpdateConstraintsはパフォーマンス良いと説明されている。
だけど呼ばなくても最近のiPhoneでは動く。
そしてUIベースのアプリは、これが必要なほど凝ったUIは作るべきではないと私は思う。
かろうじて使うのはlayoutIfNeededだろうか。