14
8

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.

systemLayoutSizeFittingでアスペクト比制約があるViewの高さを計算する

Last updated at Posted at 2020-06-30

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)
    }
}
Screen Shot 2020-06-30 at 15.18.59.png

上記の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) {}
}
Screen Shot 2020-06-25 at 18.03.43.png

このように多くの場合はsystemLayoutSizeFittingSize:で期待した通りの高さを得ることができます。ただし、Aspect Ratio制約を使っている場合は注意が必要です。
例えば以下のようなViewの場合です。(青いViewは1:1のAspect Ratio制約をかけて正方形にしています)
このUILabelに入る文字列の長さや指定した幅の大きさによっては、小さいサイズに縮小されてしまい、期待する高さを得られないことがあります。

Screen Shot 2020-06-30 at 15.23.15.png
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
                )
            )
    }
}
Screen Shot 2020-06-30 at 15.30.01.png

そのような場合は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
        )
    }
Screen Shot 2020-06-30 at 15.33.56.png

こちらにこれらのコードを含んだサンプルプロジェクトを公開しました。気になる方は、ぜひ確認してみてください。
https://github.com/maoyama/SystemLayoutSizeFittingSample

14
8
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
14
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?