この記事について
デバッグによって得られたテーブルビューセルの高さの挙動についてのメモです。
環境
Xcode Version 11.5 (11E608c)
Self-sizing Table View Cells
UITableView は次のような設定によって、オートレイアウトベースでセルの高さを決定できるようになります。
tableView.estimatedRowHeight = 85.0
tableView.rowHeight = UITableView.automaticDimension
要点は2点です。
-
estimatedRowHeight
に0
以外の適切な値を設定する -
rowHeight
にUITableView.automaticDimension
を設定する
この設定によって、オートレイアウトベースでセルの高さを決定することができます。あとは、セルのコンテンツビュー内のレイアウト制約を過不足なく配置するだけです。
このように2つのラベルを配置して、親ビュー(コンテンツビュー)と兄弟ビューとの余白を設定すれば、次のような具合でレイアウトされると思います。
UIView-Encapsulated-Layout-Height
デバッグでレイアウト制約を確認してみると、身に覚えのないレイアウト制約が設定されていると思います。
実はこの制約がセルのコンテンツビューの高さを決定しているみたいです。
オートレイアウトベースでセルの高さを決定する場合は、このレイアウト制約は次のメソッドに依存しているみたいでした。
func systemLayoutSizeFitting(
_ targetSize: CGSize,
withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority,
verticalFittingPriority: UILayoutPriority
) -> CGSize
systemLayoutSizeFitting
は、対象サイズとそのレイアウト優先度をパラメータとして指定することで、制約ベースにフィットするサイズを算出メソッドです。
試しに本来の処理を無視して、高さを固定してみます。
import UIKit
class CustomCell: UITableViewCell {
@IBOutlet weak var label1: UILabel!
@IBOutlet weak var label2: UILabel!
override func systemLayoutSizeFitting(
_ targetSize: CGSize,
withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority,
verticalFittingPriority: UILayoutPriority
) -> CGSize {
var size = super.systemLayoutSizeFitting(
targetSize,
withHorizontalFittingPriority: horizontalFittingPriority,
verticalFittingPriority: verticalFittingPriority
)
size.height = 100
return size
}
}
オートレイアウトベースで決定するはずのセルの高さが固定になりました。
つまり、オートレイアウトベースで高さが決まるということは、『systemLayoutSizeFitting でセルのサイズを決める』ということだったんですね。
Self-sizing で実装するときの注意点
systemLayoutSizeFitting
をオーバーライドしてブレークポイントを貼るなどして、どのようなパラメータが渡されるのかを確認することができます。
iPhone SE (2nd generation) の場合
targetSize | horizontalFittingPriority | verticalFittingPriority |
---|---|---|
(375.0, 0.0) | 1000 | 50 |
対象サイズの高さを 0
、垂直方向のレイアウト優先度を 50
にしていました。垂直方向の優先度を低く設定することで、コンテンツビュー内のレイアウト制約を優先させているようです。
こうすることで、対象サイズの高さをブレイクさせてフィットする高さを計算させているみたいです。
以上のことから、コンテンツビュー内部に配置したビューのレイアウト制約の優先度を 50 未満にしてしまうと意図しない高さになるかもしれないこともわかりました。
たとえば次のように、ラベルとコンテンツの下部の制約の優先度を 49 に変更してみます。
この場合、verticalFittingPriority
の 50 が優先されるので意図しないレイアウトになりました。
通常は、50
未満の優先度を指定することはないと思いますが、優先度の設定には十分に気をつけたほうが良さそうですね。
これは iOS SDK の挙動なので、これに従うのがベターだと思いますが、もしも verticalFittingPriority
の 50
が都合悪い場合は、これをオーバーライドすることで意図したセルの高さにすることができました。
override func systemLayoutSizeFitting(
_ targetSize: CGSize,
withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority,
verticalFittingPriority: UILayoutPriority
) -> CGSize {
return super.systemLayoutSizeFitting(
targetSize,
withHorizontalFittingPriority: horizontalFittingPriority,
verticalFittingPriority: UILayoutPriority(1)
)
}
こうすれば、インターフェースビルダーなどで配置したビューのレイアウト制約の優先度を 50
未満にしても意味のある値になると思います。
まとめ
- オートレイアウトベースで高さを決めるということは、
systemLayoutSizeFitting
でセルの高さを決めるということだった - UIView-Encapsulated-Layout-Height は上記が算出するサイズの高さに一致する
- この計算では、
verticalFittingPriority
に50
が設定される - この点に気をつければ、テーブルビューセルの意図しない制約のブレイクを避けることができる