13
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Life is Tech ! #1Advent Calendar 2020

Day 22

[AutoLayout] Intrinsic Content Sizeを活用しよう〜その1 概要編〜

Last updated at Posted at 2020-12-21

始めに

こんにちは、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を一つ配置し、以下のようにcenterXcenterYの制約を付与します。

centerXとcenterYのAttributeを持つ制約の追加
add_constraints_new.gif

上の操作によってcenterXcenterYの制約をUILabelに追加したため、Phone 12 Pro Maxで実行してもUILabelは画面の中央に配置されます。

制約を追加後のUILabel

上で説明したように、制約はx軸で2つy軸で2つの合計4つ必要ですが、centerXcenterYの2つのみに制約を付与した場合でも正常に動作します。
これはIntrinsic Content Sizeが制約として働くためです。

Sample1のUILabelの制約

上の画像からも、Storyboard上では二つしか制約を付与していないはずなのに、実際には制約は4つ有効になっていることが分かると思います。

ただし、Labelの文字が長くなって画面の枠からはみ出してしまう場合は、以下のように端からの最小距離を設定しておくと必要があります。

Intrinsic Content Sizeが画面よりも大きい場合
Storybaord上で端からの最小距離を32ptに設定 実際の動作

また、centerXcenterYではなく、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は維持されます。今回はUILabelContent 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 PriorityContent Compression Resistance Priorityを確認する必要があります。


ここまでを理解すればIntrinsic Content Sizeはもう理解したと言っても過言ではないと思います!
これからは、実際に自分でレイアウトを組んでトライアンドエラーをして経験を積んでいきましょう!😎

この記事を読んでIntrinsic Content Sizeを少しでも理解してもらえたら最高!
undraw_order_confirmed_aaw7.png

|


ここまで読んでいただたきありがとうございます!最後に発展としていくつかセクションがあるので余力があれば合わせて読んでみてください😁

発展

発展

UIView.noIntrinsicMetric

標準の段階でも全てのUIViewが理想的なサイズ(Intrinsic Content Size)を設定できているわけではありません。
例えば、ただのUIViewやUIScrollViewは中身が不明であったりスクロールできる故に中身のサイズ≠コンテナのサイズという様なことが考えられます。
そのようなUIViewのIntrinsic Content Sizewidthheightも共にUIView.noIntrinsicMetricという値に設定されています。

Intrinsic Content Sizeが無効なViewの一例
UIView
UITableView
UICollectionView
UIStackView
UITextView(スクロール可能な場合)

UITableViewUICollectionViewやスクロール可能な場合のUITextViewUIScrollViewを継承しているという共通点があります。

Intrinsic Content Sizeをカスタマイズする

上記のようなIntrinsic Content Sizeが無効なUIViewに対しても、Intrinsic Content SizeUIViewのプロパティであるため、クラスを継承すれば自分でカスタマイズをして有効にすることもできます。
例えば、以下のようにUIViewを継承したHalfModalViewIntrinsic 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編〜も合わせて読んでみてください〜
最後まで読んでくださりありがとうございました!

13
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?