UICollectionViewCellなどで、表示する内容とAutoLayoutの制約から高さを計算したいことは、よくあると思います。
そのような時は以下のようにsystemLayoutSizeFittingSize:を使うだけで、簡単に高さを得ることができます。
class SampleCell: UICollectionViewCell {
@IBOutlet weak var label: UILabel!
private static var layoutCell: SampleCell = {
UINib(nibName: "SampleCell", bundle: nil)
.instantiate(withOwner: nil, options: nil)[0] as! SampleCell
}()
static func fittingSize(from model: String, with containerWidth: CGFloat) -> CGSize {
layoutCell.label.text = model
layoutCell.frame.size = CGSize(width: containerWidth, height: .greatestFiniteMagnitude)
layoutCell.setNeedsLayout()
layoutCell.layoutIfNeeded()
return layoutCell.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
}
}
上記のxibとコードを、Xcode Previewsで次のように確認すると高さの計算がうまくいってることがわかります。
struct SampleCell_Previews: PreviewProvider {
static var model = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
static var previews: some View {
let cell = UINib(nibName: "SampleCell", bundle: nil)
.instantiate(withOwner: nil, options: nil)[0] as! SampleCell
cell.label.text = model
let view = Wrap(uiView: cell)
return view
.previewLayout(
.fixed(
width: 320,
height: SampleCell.fittingSize(from: model, with: 320).height
)
)
}
}
struct Wrap<T: UIView>: UIViewRepresentable {
var uiView: T
func makeUIView(context: Context) -> T { uiView }
func updateUIView(_ view: T, context: Context) {}
}
このように多くの場合はsystemLayoutSizeFittingSize:で期待した通りの高さを得ることができます。ただし、Aspect Ratio制約を使っている場合は注意が必要です。
例えば以下のようなViewの場合です。(青いViewは1:1のAspect Ratio制約をかけて正方形にしています)
このUILabelに入る文字列の長さや指定した幅の大きさによっては、小さいサイズに縮小されてしまい、期待する高さを得られないことがあります。
class SampleCell2: UICollectionViewCell {
@IBOutlet weak var label: UILabel!
private static var layoutCell: SampleCell2 = {
UINib(nibName: "SampleCell2", bundle: nil)
.instantiate(withOwner: nil, options: nil)[0] as! SampleCell2
}()
static func fittingSize(from model: String, with containerWidth: CGFloat) -> CGSize {
layoutCell.label.text = model
layoutCell.frame.size = CGSize(width: containerWidth, height: .greatestFiniteMagnitude)
layoutCell.setNeedsLayout()
layoutCell.layoutIfNeeded()
return layoutCell.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
}
}
struct SampleCell2_Previews: PreviewProvider {
static var model = "Lorem ipsum"
static var previews: some View {
let cell = UINib(nibName: "SampleCell2", bundle: nil)
.instantiate(withOwner: nil, options: nil)[0] as! SampleCell2
cell.label.text = model
let view = Wrap(uiView: cell)
return view
.previewLayout(
.fixed(
width: 320,
height: SampleCell2.fittingSize(from: model, with: 320).height
)
)
}
}
そのような場合はsystemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority:を使い、HorizontalFittingPriorityをrequiredにすると指定した幅が優先されます。これでUILabelに入る文字列の長さや指定した幅に依存せず、期待した高さを得ることができます。
static func fittingSize(from model: String, with containerWidth: CGFloat) -> CGSize {
layoutCell.label.text = model
layoutCell.frame.size = CGSize(width: containerWidth, height: .greatestFiniteMagnitude)
layoutCell.setNeedsLayout()
layoutCell.layoutIfNeeded()
// return layoutCell.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
return layoutCell.systemLayoutSizeFitting(
UIView.layoutFittingCompressedSize,
withHorizontalFittingPriority: .required,
verticalFittingPriority: .fittingSizeLevel
)
}
こちらにこれらのコードを含んだサンプルプロジェクトを公開しました。気になる方は、ぜひ確認してみてください。
https://github.com/maoyama/SystemLayoutSizeFittingSample