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

UICollectionViewCellなどで、表示する内容とAutoLayoutの制約から高さを計算したいことは、よくあると思います。
そのような時は以下のようにsystemLayoutSizeFittingSize:を使うだけで、簡単に高さを得ることができます。

```swift
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)
    }
}
```
<img width="787" alt="Screen Shot 2020-06-30 at 15.18.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151036/15db0392-db6d-7e83-116d-f80469488e3b.png">

上記のxibとコードを、Xcode Previewsで次のように確認すると高さの計算がうまくいってることがわかります。

```swift
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) {}
}
```

<img width="425" alt="Screen Shot 2020-06-25 at 18.03.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151036/151ef51c-ecd0-b4b2-15c3-119d6cc70511.png">


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

<img width="801" alt="Screen Shot 2020-06-30 at 15.23.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151036/760052f6-5218-2eef-12d1-84f68ec06abb.png">

```swift
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
                )
            )
    }
}
```
<img width="435" alt="Screen Shot 2020-06-30 at 15.30.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151036/9409aca5-1fcc-9d28-5bae-d4676029d0a3.png">

そのような場合はsystemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority:を使い、HorizontalFittingPriorityをrequiredにすると指定した幅が優先されます。これでUILabelに入る文字列の長さや指定した幅に依存せず、期待した高さを得ることができます。

```swift
    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
        )
    }
```
<img width="449" alt="Screen Shot 2020-06-30 at 15.33.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151036/264f818b-e37b-e5e1-41b3-0bcd881bc26e.png">


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