🦔

AutoLayoutがうまく動かないときにやること

2022/11/24に公開約2,800字

AutoLayoutが思ったように動かないとき、ありますよね?
そんなときに効くかもしれないことを書いていきます。

Intrinsic Content Sizeを意識する

AutoLayoutで制約つけているのに、なんか思ってるのと大きさ違うぞ?というときは、Intrinsic Content Sizeが優先されている場合があります。
(Intrinsicって単語、難しすぎて未だにググらないと書けません。AutoLayout関連の英単語、難しすぎる)

Intrinsicは「固有の」という意味です。
そのUI要素が必要とする最小サイズです。
たとえば UILabel で、フォントサイズいくつで、何文字指定したかが決まれば、そのラベルの表示にどのぐらいwidthが必要かは算出できます。

詳しくは↓の記事なんかがいいと思います。

https://qiita.com/shtnkgm/items/f0b189e4184fe6c90707

やり方次第ではIntrinsic Content Sizeを無視して制約を優先することも可能ではありますが、それは悪い制約の付け方なので、
基本的にはIntrinsic Content Sizeがあることを意識して制約をつけるべきです。

Content Hugging Priority

StackViewに要素を入れて、それに対して制約つけていくのはよくあるパターンです。
そのときにこんな風に、要素が均等にならなかった経験はありませんか?

.fill を指定しないで、.equalSpacing とか、 .fillProportionally とかを指定すればマシになりますが、それでも思ったようなサイズにできませんでした。

これ長年なんだろうなあと思ってたんですが、Content Hugging PriorityとContent Compression Resistance Priorityを指定していなかったせいでした。
StackViewは、まずIntrinsic Content Sizeでコンテンツを並べて、余ったスペースを調整する際に、この2パラメーターを見ています。

まずContent Hugging Priorityについて。
これは要素の「広がりにくさ」の優先度です。
Huggingは意味わからなくて調べたんですが、たぶん「ハグ」のHuggingなんだと思います。
抱き締める優先度→元のサイズから広がろうとしたときにブロックする優先度 みたいな解釈なのかと思います。

0から1,000で指定する数値で、UIKitのデフォルト値は↓のQiitaを見てください。

https://qiita.com/shtnkgm/items/f0b189e4184fe6c90707#content-hugging-priorityとcontent-compression-resistance-priority

「広がりにくさ」なので、たとえばUISwitchのような明らかに引き伸ばしたらおかしい要素は高めに設定されていて、
UILabelやUIButtonのように、どちらかといえば引き伸ばして欲しい(ケースバイケースですが)やつは低めに設定されています。

試しに左ボタンに、高い数値を指定してみます。

button.setContentHuggingPriority(.required, for: .horizontal)

.required だと優先度は1,000です。

これだと引き伸ばされないので、左ボタンはIntrinsic Content Size通りに表示されます。
ちなみにStackViewの .fillは、優先度が同じだった場合は左の要素を引き伸ばし対象にするそうです。

Content Compression Resistance Priority

たぶんContent Hugging Priorityよりもこっちの方がわかりやすいと思います。
Content Compression Resistance Priorityは、その名の通り「コンテンツの圧縮抵抗優先度」です。

StackViewの中に要素がいくつもある場合、この要素は表示しておきたい、この要素は省略OK、みたいな優先度ってあると思います。
そんなときに、Content Compression Resistance Priorityを設定します。

setContentCompressionResistancePriority(.required, for: .horizontal)
setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
setContentCompressionResistancePriority(.defaultLow, for: .horizontal)

上に行けば行くほど、優先度が高くなります。

昔AutoLayoutをよくわかってなかったとき、どうしてもデザイン上省略されて欲しくないラベルがあって、
結局UILabel+StackViewにして、ラベルを外出しした経験があります。
今ふりかえると、そんなことしなくてもContent Compression Resistance Priorityを適切に設定してあげれば実現できたな〜と思います。

更新されない制約を無理やり更新させる

Viewのライフサイクルの関係で、制約の更新が反映されないことがあります。
AutoLayout絡みでデバッグしてるときは特に、自分の制約設定がまずいのか、
設定はあってるけどタイミングの問題で反映されていないのかを切り分けたくなります。
あるいは本気でどう見てもAutoLayoutが正しくつけられているはずなのに、思ったように反映してくれないとき。

そんなときにライフサイクルを無視して、無理やり反映させる方法があります。

setNeedsLayout()
layoutIfNeeded()

これで制約の更新ができます。
ただあくまで最終手段です。

(了)

Discussion

ログインするとコメントできます