iOSのAutoLayoutにおけるIntrinsic Content Sizeについて

# はじめに

本記事ではiOSのAutoLayoutを理解するのに欠かせない、**「Intrinsic Content Size」**についての解説を行います。SwiftUIが話題になっていますが、ここ数年は既存のAutoLayoutを前提としたレイアウトもまだまだ多いと考えられるため、知っておいて損はない情報かと思います。

# Intrinsic Content Sizeとは

Intrinsicはあまり聞き慣れない英単語ですが、「固有の」、「本来備わっている」といった意味を持ち、**あるビューのコンテンツを表示するための最低のサイズ**を表します。

例えば、UILabelでは表示する文字列やフォントで表示する固有のサイズが決まり、UIImageViewでは表示する画像のサイズで表示する固有のサイズが決まります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113553/fa18d8b7-71a6-6db7-0bcc-a7c246d99934.png" width="350px">

**AutoLayoutを利用したレイアウトの場合、Intrinsic Content Sizeを考慮してレイアウト計算**が行われます。これによって表示コンテンツのサイズを個別に指定せずともうまく表示される仕組みになっています。逆にもうまくいかないこともあるため、Intrinsic Content Sizeがどのように働くかを理解しておく必要があります。

# Intrinsic Content SizeはUIViewクラスのプロパティである

UIViewクラスには`intrinsicContentSize`というIntrinsic Content Sizeを表すプロパティがあります。

```swift
var intrinsicContentSize: CGSize { get }
```

https://developer.apple.com/documentation/uikit/uiview/1622600-intrinsiccontentsize

このプロパティは読み取り専用のプロパティです。通常はintrinsicContentSizeを取得することにしか利用できませんが、UIViewのサブクラスでintrinsicContentSizeをオーバーライドすることでintrinsicContentSizeを変更することもできます。

例えば、内部に余白を持ったUILabelを作りたい場合は以下のように定義することができます。イイ感じのバッジUIをかんたんに作るためのTipsです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113553/67b6eb15-58b3-e4cf-f05a-aabdd949a1d1.png" width="300px">

```swift
// 内部に余白を持ったUILabel
public class PaddingLabel: UILabel {
    // 余白のサイズ
    public let padding: UIEdgeInsets = .zero
    
    // テキストの描画範囲を余白のInsetを加えたものにする
    public override func drawText(in rect: CGRect) {
        super.drawText(in: rect.inset(by: padding))
    }
    
    // Intrinsic Content Sizeを余白のInset加えたものにする
    public override var intrinsicContentSize: CGSize {
        var intrinsicContentSize = super.intrinsicContentSize
        intrinsicContentSize.height += padding.top + padding.bottom
        intrinsicContentSize.width += padding.left + padding.right
        return intrinsicContentSize
    }
}
```

```swift
// 上下に4px、左右に8pxの余白を設定する
paddingLabel.padding = UIEdgeInsets(top: 4, left: 8, bottom: 4, right: 8)
```

# Intrinsic Content SizeはStoryBoard/XIBで一時的に設定できる

Intrinsic Content SizeはStoryBoard/XIBで一時的に設定できます。この設定は実行時には影響しません。コンテンツはAPIから取得したりなど、動的に変化するものですが、StoryBoard/XIBでの暫定的なレイアウトを行うためにPlaceholderとして設定できるようです。

Intrinsic SizeをDefault（System Defined）からPlaceholderにすることで設定が可能。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113553/53b77672-905e-bfd3-e8b9-be2db38b68a7.png" width="350px">


# Content Hugging PriorityとContent Compression Resistance Priority

Intrinsic Content Sizeに関連するものとして、**Content Hugging Priority**と**Content Compression Resistance Priority**があります。これらは略して**CHCR Priority**とも呼ばれます。

**Content Hugging Priority**（コンテンツに沿う優先度）はコンテンツサイズよりも大きくなりにくさ、**Content Compression Resistance Priority**（コンテンツの圧縮抵抗優先度）はコンテンツサイズよりも小さくなりにくさを表す優先度です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113553/61d105a4-273c-564b-f001-243220091ab9.png" width="350px">

Content Compression Resistance Priorityは一律750がデフォルト値になりますが、Content Hugging Priorityは
Viewのサブクラスによって値が異なります。例えばUITextViewやUIButtonは250ですが、UILabelやUIImageViewは251です。さらに、UISwitchやUIActivityIndicatorViewなどコンテンツサイズよりも小さくすることができないクラスについてはContent Hugging Priorityは750になります。

|クラス|Content Hugging Priority|Content Compression Resistance Priority|
|:-:|:-:|:-:|
|UITextView|250|750|
|UIButton|250|750|
|UILabel|251|750|
|UIImageView|251|750|
|UISwitch|750|750|
|UIActivityIndicatorView|750|750|

制約（NSLayoutConstraint）のデフォルトの優先度は1000であるため、明示的にコンテンツサイズを制約で指定した場合は制約のコンテンツサイズが優先されます。
Content Hugging PriorityとContent Compression Resistance Priorityは、Viewにサイズを決定するための十分な制約がない場合に**NSContentSizeLayoutConstraint**という制約をつくるのに利用されます。

NSContentSizeLayoutConstraintは以下の制約となります。

 - ViewのサイズがintrinsicContentSize以下（優先度：Content Hugging Priorityの値）
 - ViewのサイズがintrinsicContentSize以上（優先度：Content Compression Resistance Priorityの値）

※NSContentSizeLayoutConstraintとNSLayoutConstraintの優先度が同じ場合はNSLayoutConstraintが優先されます。

# まとめ

 - Intrinsic Content Sizeはあるビューのコンテンツを表示するための最低のサイズ
 - Intrinsic Content SizeはintrinsicContentSizeプロパティを継承することで設定可能
 - Viewのサイズを決定するための制約が不足している場合にIntrinsic Content SizeとContent Hugging PriorityとContent Compression Resistance Priorityの値を元に、NSContentSizeLayoutConstraintという制約が自動で設定される

# 参考

 - [Apple - Auto Layout Guide
](https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/index.html#//apple_ref/doc/uid/TP40010853-CH7-SW1)
 - [よくわかるAuto Layout iOSレスポンシブデザインをマスター](http://www.ric.co.jp/book/contents/book_1032.html)