このような性質のカスタムViewがあるとします。
- Viewの幅が決まると同時に高さも決まる
- Viewの幅はSuperview上のConstraintで決められ、自身では決められない
複数行表示のUILabelもその類です。自分でカスタムViewを作る時は、intrinsicContentSizeで適切なサイズを返すように実装すると良いです。
intrinsicContentSizeの実装
仮にViewの幅に対して、高さを2/3とするカスタムViewを作るとします。その場合、以下のように実装できます。
override var intrinsicContentSize: CGSize {
guard let viewWidth = self.superview?.frame.size.width else {
return CGSize(width: UIViewNoIntrinsicMetric, height: UIViewNoIntrinsicMetric)
}
return CGSize(width: UIViewNoIntrinsicMetric, height: viewWidth * 2/3)
}
- superviewの幅を取得する
- superviewがnilの時は、UIViewNoIntrinsicMetricを返す
- 実際には、intrinsicContentSizeが呼ばれるタイミングでsuperviewがnilになることはないと思われるが念のため
- その幅を元に高さを計算し、CGSizeで返す
- 幅は自身で決めるものではないので、UIViewNoIntrinsicMetricを返す
例なので簡単なものにしていますが、実際は、UILabelなど複数のViewを配置し、サイズが可変するようなカスタムViewを作る場合があると思います。その時は、UILabel#sizeThatFitsを呼ぶなどして、適切なサイズを計算し返す必要があります。
Interface Builder上での配置
仮にこのカスタムViewのクラス名をMyCustomViewとします。
- Interface BuilderでViewをSuperviewに配置
- ViewのIdentity InspectorのClassにMyCustomViewと入力
- そのViewのHeight Constraintとして以下の2つを付ける
- RelationをEqual、Constantを取りうる最小の高さ、PriorityをHigh(750)にしたもの
- RelationをGreater Than or Equal、Constantを取りうる最小の高さ、PriorityをRequired(1000)にしたもの
このようにすると、カスタムViewはinstrinsicContentSizeに従って、自動的にリサイズされるようになります。
動的に高さが変わる場合
プロパティを設定し直すと、カスタムViewの表示内容が変更され、サイズが変わる場合があると思います。その場合は、invalidateIntrinsicContentSize()を呼びます。そうすると、intrinsicContentSizeが適切なタイミングで呼ばれリサイズします。
多層的に配置するとうまくリサイズされない場合
UITableViewCellなどにカスタムViewを配置すると、Viewのサイズ(frame.size)が最終決定するタイミングとintrinsicContentSizeが呼び出されるタイミングにズレが生じ、うまくリサイズされないことがあります。その場合は、layoutSubviewsでinvalidateIntrinsicContentSizeを呼ぶと解決します。ただし、良い手かどうかはわかりませんので自己責任で・・・。自分の環境では問題は起きていません。
override func layoutSubviews() {
invalidateIntrinsicContentSize()
super.layoutSubviews()
}