始めに
こんにちは、Life is Tech ! #1 Advent Calendar 2020の22日目を担当しますふみっちです。
今回はiOSアプリ開発におけるIntrinsic Content Size
に関する知見が最近深まってきたのでそれに関して話していけたらいいなと思います。どうぞお付き合いください。
また、この記事は〜その1 概要編〜となっていて、Intrinsic Content Size
に関する説明を行います。実装とそれに関する解説はLife is Tech ! #2 Advent Calendar 2020 22日目に書かせてもらったので合わせて確認してもらえると嬉しいです☺️
主題
AutoLayoutは制約を用いてUIView同士の相対的な距離を決定します。その制約は一度に有効となる個数に限りがあります。
いくつの制約を付与すればいいのかは作成したいレイアウトによって様々ですが、「このLabelはどのくらいの高さにしようか」といった1ptレベルでの細かい数字の決めであったり、「このLabelは最初は1行だけど、ボタンを押したら2行にしたいな」といった動的なサイズの変化に関しては表示するデータの内容を考慮する必要があり、実装が複雑になることもあると思います。
Intrinsic Content Size
を活用することで、横幅や高さを具体的に指定する必要がなくなったり、動的なサイズの変化に対応することができます。
そこで今回はIntrinsic Content Size
の概要を説明します!
AutoLayoutに必要な制約の個数
一つのUIViewが有効にできる制約の個数はx軸で2つ、y軸で2つです。(ただ、例外として、手元で確認したところUIStackViewに関してはx軸・y軸ともに3つの制約が確認できました。)
また、同じAttributeに関する制約はいくら付与しても有効になるのは一つです。なので、width
の制約を2つ付与したとしてもx軸に関して2つの制約を付与したことにはならない点に注意が必要です。
例えば以下の組み合わせは全て軸に対して2つの制約を有効にすることができます。
x軸 | y軸 |
---|---|
width centerX
|
height centerY
|
width trailing
|
height top
|
leading centerX
|
bottom centerY
|
一方で、以下の制約は同じAttributeに対しての制約となるためどちらか一方しか有効にならず、x軸で2つの制約を追加したことになりません。
// item2の横幅の2倍の長さに30を足した大きさを横幅に設定
item1のwidthに関するConstraint = item2のwidthに関するAttribute * 2 + 30
// item2の横幅に90を足した大きさを横幅に設定
item1のwidthに関するConstraint = item2のwidthに関するAttribute * 1 + 90
必要以上に制約を付与した場合
次に、必要以上に制約を付与した場合に関して、実際の挙動と共に確認します。
UILabelにx軸に関して3つ、y軸に関して3つの制約を付与してみましょう。
UILabelに合計6個の制約を付与 |
---|
Xcode上ではエラーもなく正しく動いているように見えますが、実際には制約はそれぞれの軸で2つしか有効でないため、下の画像のように無視される制約(薄くなっている制約)が発生します。
実際に有効な制約 |
---|
また、デバッグ前にXcode上で上記の問題を検知したい場合は、Storyboard上のiPhoneの種類を変更してみるのが手軽です。
ここでは触れませんが、StorybaordのPreviewを機能を用いると複数のサイズを一度に確認することもできます。
iPhone SE 2→iPhone 11に変更 |
---|
制約の個数がx軸に2つy軸に2つに満たない場合
今度は逆に制約の個数が4つに満たない場合を考えてみましょう。
この場合は、Intrinsic Content Size
についても考慮する必要があります。
Intrinsic Content Sizeとは
Intrinsic Content Size
とはUIViewの理想的なサイズのことです。例えば、UILabelに関してはテキストが過不足なく収まるサイズということになります。
通常のUILabelにおけるIntrinsic Content Size | 複数行の場合は自動でリサイズされる |
---|---|
Intrinsic Content Sizeは制約となる
Sample1として例をみていきましょう
画面中央にUILabelを一つ配置し、以下のようにcenterX
とcenterY
の制約を付与します。
centerXとcenterYのAttributeを持つ制約の追加 |
---|
上の操作によってcenterX
とcenterY
の制約をUILabelに追加したため、Phone 12 Pro Maxで実行してもUILabelは画面の中央に配置されます。
制約を追加後のUILabel |
---|
上で説明したように、制約はx軸で2つy軸で2つの合計4つ必要ですが、centerX
とcenterY
の2つのみに制約を付与した場合でも正常に動作します。
これはIntrinsic Content Size
が制約として働くためです。
Sample1のUILabelの制約 |
---|
上の画像からも、Storyboard上では二つしか制約を付与していないはずなのに、実際には制約は4つ有効になっていることが分かると思います。
ただし、Labelの文字が長くなって画面の枠からはみ出してしまう場合は、以下のように端からの最小距離を設定しておくと必要があります。
Intrinsic Content Sizeが画面よりも大きい場合 |
---|
Storybaord上で端からの最小距離を32ptに設定 | 実際の動作 |
---|---|
また、centerX
やcenterY
ではなく、width
またはheight
に関しての制約を付与した場合はIntrinsic Content Size
による制約とAttribute
が重複してしまうため、Intrinsic Content Size
が無効になってしまう点に注意が必要です。
Storyboard | Debug |
---|---|
Content Hugging Priority
Intrinsic Content Size
を持つ複数のUIViewに対して表示領域(画面サイズなど)が大きい場合にContent Hugging Priority
を気にする必要があります。
Content Hugging Priority
が大きいほど、他のUIViewよりもIntrinsic Content Size
が優先され理想的なサイズを維持することができます。
一方、Content Hugging Priority
が小さいほど、Intrinsic Content Size
は無視されやすく、表示領域が大きいため理想的なサイズよりも大きくなります。
以下がそれぞれのUIViewが持つContent Hugging Priority
の値の一覧です。(一部)
部品名 | Content Hugging Priority |
---|---|
UILabel | 251 |
UIImageView | 251 |
UITextField | 250 |
UIButton | 250 |
UIPickerView | 250 |
同じContent Hugging Priority
を並べて制約を過不足なく追加すると、優先度(Priority)を判断できずエラーが発生します。
例えば、UILabelとUIImageViewは共にContent Hugging Priority
が251に設定されているため以下のように制約エラーが発生してしまいます。
理想的なサイズよりも画面領域が広いためUIViewを理想的なサイズで表示できずエラーが発生 |
---|
制約 | エラー内容 |
---|---|
このようなエラーが発生した場合は関係するUIView同士が異なるContent Hugging Priority
の値を持つように変更してあげます。
説明したようにContent Hugging Priority
が大きいほどIntrinsic Content Size
は維持されます。今回はUILabel
のContent Hugging Priority
を1上げて252にしてあげます。
変更後のUILabel | 変更後のUIImageView |
---|---|
Content Compression Resistance Priority
Intrinsic Content Size
を持つ複数のUIViewに対して表示領域(画面サイズなど)が小さい場合にContent Compression Resistance Priority
を気にする必要があります。
Content Compression Resistance Priority
が大きいほど、他のUIViewよりもIntrinsic Content Size
が優先され理想的なサイズを維持することができます。
一方、Content Compression Resistance Priority
が小さいほど、Intrinsic Content Size
は無視されやすく、表示領域が小さいため理想的なサイズよりも小さくなります。
以下がそれぞれのUIViewが持つContent Compression Resistance Priority
の値の一覧です。(一部)
部品名 | Content Compression Resistance Priority |
---|---|
UILabel | 750 |
UIImageView | 750 |
UITextField | 750 |
UIButton | 750 |
UIPickerView | 750 |
基本的に全てのUIViewは一律でContent Compression Resistance Priority
は750に設定されています。
今回は、UILabelとUIImageViewのグループを3つ縦に並べる場合を考えます。本来は全てのUIViewのIntrinsic Content Size
を尊重したいですが、そうすると以下のように画面に収まりきらずエラーが発生してしまいます。
画面に部品が収まりきらずエラーが発生 |
---|
制約 | エラー内容 |
---|---|
Content Compression Resistance Priority
が全て750となっているため、どのUIViewを縮小すれば良いかを判断できないためです。
なので、一番下のUIImageViewのContent Compression Resistance Priority
を750から749に変更して、制約に合わさるサイズで表示します。
一番下のUIImageViewのContent Compression Resistance Priority を749に変更 |
---|
このように複数のIntrinsic Content Size
を考慮する必要がある場合はContent Hugging Priority
やContent Compression Resistance Priority
を確認する必要があります。
ここまでを理解すればIntrinsic Content Size
はもう理解したと言っても過言ではないと思います!
これからは、実際に自分でレイアウトを組んでトライアンドエラーをして経験を積んでいきましょう!😎
この記事を読んでIntrinsic Content Sizeを少しでも理解してもらえたら最高! |
---|
|
ここまで読んでいただたきありがとうございます!最後に発展としていくつかセクションがあるので余力があれば合わせて読んでみてください😁
発展
発展
UIView.noIntrinsicMetric
標準の段階でも全てのUIViewが理想的なサイズ(Intrinsic Content Size
)を設定できているわけではありません。
例えば、ただのUIViewやUIScrollViewは中身が不明であったりスクロールできる故に中身のサイズ≠コンテナのサイズという様なことが考えられます。
そのようなUIViewのIntrinsic Content Size
はwidth
もheight
も共にUIView.noIntrinsicMetric
という値に設定されています。
Intrinsic Content Size が無効なViewの一例 |
---|
UIView |
UITableView |
UICollectionView |
UIStackView |
UITextView(スクロール可能な場合) |
UITableView
やUICollectionView
やスクロール可能な場合のUITextView
はUIScrollView
を継承しているという共通点があります。
Intrinsic Content Sizeをカスタマイズする
上記のようなIntrinsic Content Size
が無効なUIViewに対しても、Intrinsic Content Size
はUIView
のプロパティであるため、クラスを継承すれば自分でカスタマイズをして有効にすることもできます。
例えば、以下のようにUIView
を継承したHalfModalView
にIntrinsic Content Size
を設定することもできます。
class HalfModalView: UIView {
override var intrinsicContentSize: CGSize {
let height: CGFloat = UIScreen.main.bounds.size.height / 2
let width: CGFloat = UIScreen.main.bounds.size.width
return CGSize(width: width, height: height)
}
}
上のHalfModalViewはIntrinsic Content Size
が有効なため、追加する制約はx軸に一つy軸に一つで問題ありません。
また、以下のPaddingLabel
は通常のUILabel
に比べて、中央揃えの場合には、右に16pt
左に16pt
、上に8pt
下に8pt
の余白をつけます。
class PaddingLabel: UILabel {
override var intrinsicContentSize: CGSize {
var size: CGSize = super.intrinsicContentSize
size.width += 32
size.height += 16
return size
}
}
最後に
自分はIntrinsic Content Size
を知ってからAutoLayoutの制約の組み方が劇的に分かるようになったので、同じようにこの記事をみて少しでもIntrinsic Content Size
に興味が生まれたり関心が高まるととても嬉しいです!🎉
具体的な実装例として[AutoLayout] Intrinsic Content Sizeを活用しよう〜その2 Self Sizing編〜も合わせて読んでみてください〜
最後まで読んでくださりありがとうございました!