StackViewのfillProportionallyが思い通りに動かなかった話

## StackView.destributionわかったかも！

「StackViewのfillProportionallyちょっとわかったかも。要はStackViewの大きさと中身が揃ってない時中身の比を考慮して敷き詰めてくれるって感じね。Playgroundsでサンプル作って動かそ」



<details><summary>5つのUIViewを用意してwidthAnchorとheightAnchorをそれぞれを100pt×100ptにして、`addArangeSubView`して。。。</summary><div>

```swift
import PlaygroundSupport
import UIKit

class View: UIView {
    let stackView = UIStackView()
    let redView = UIView()
    let orangeView = UIView()
    let yellowView = UIView()
    let greenView = UIView()
    let blueView = UIView()
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .white
        setupStackView()
        setupColorViews()
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    private func setupStackView() {
        stackView.backgroundColor = .white
        stackView.axis = .horizontal
        stackView.alignment = .fill
        stackView.distribution = .fillProportionally
        addSubview(stackView)
        stackView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            stackView.centerYAnchor.constraint(equalTo: self.centerYAnchor),
            stackView.centerXAnchor.constraint(equalTo: self.centerXAnchor),
            stackView.widthAnchor.constraint(equalTo: self.widthAnchor)
        ])
    }
    private func setupColorViews() {
        redView.backgroundColor = .red
        orangeView.backgroundColor = .orange
        yellowView.backgroundColor = .yellow
        greenView.backgroundColor = .green
        blueView.backgroundColor = .blue
        let colorViews: [UIView] = [redView,orangeView,yellowView,greenView,blueView]
        colorViews.forEach { view in
            NSLayoutConstraint.activate([
                view.widthAnchor.constraint(equalToConstant: 100),
                view.heightAnchor.constraint(equalToConstant: 100)
            ])
            stackView.addArrangedSubview(view)
        }
    }
}
```
</details></div>

![スクリーンショット 2021-05-03 14.54.08.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/062c8e05-846c-394e-a18c-e0602ab7a398.jpeg)



「あれ？？大きさを考慮して敷き詰めるなら100：100：100：100：100 = 1:1:1:1:1だから等間隔に敷き詰められるんじゃないの？」

## 調べてみると

どうやらfillProportionallyが比の参考にするのはframeのsizeではなく、intrinsicContentSizeらしい。当たり前といえば当たり前なのかも。

「なるほど。じゃあintrinsicContentSizeを100pt×100ptにしてみよ」

色と大きさを指定できるUIViewを作成

```swift
class ColorView: UIView {
    var size: CGSize
    init(color: UIColor, size: CGSize) {
        self.size = size
        super.init(frame: CGRect())
        backgroundColor = color
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    override var intrinsicContentSize: CGSize {
        return CGSize(width: size.width, height: size.height)
    }
}
```

これを水平方向の画面いっぱいに広がるStackViewに入れる

```swift
class View: UIView {
    let stackView = UIStackView()
    let redView = ColorView(color: .red, size: CGSize(width: 100, height: 100))
    let orangeView = ColorView(color: .orange, size: CGSize(width: 100, height: 100))
    let yellowView = ColorView(color: .yellow, size: CGSize(width: 100, height: 100))
    let greenView = ColorView(color: .green, size: CGSize(width: 100, height: 100))
    let blueView = ColorView(color: .blue, size: CGSize(width: 100, height: 100))
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .white
        setupStackView()
        setupColorViews()
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    private func setupStackView() {
        stackView.backgroundColor = .white
        stackView.axis = .horizontal
        stackView.alignment = .fill
        stackView.distribution = .fillProportionally
        addSubview(stackView)
        stackView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            stackView.centerYAnchor.constraint(equalTo: self.centerYAnchor),
            stackView.centerXAnchor.constraint(equalTo: self.centerXAnchor),
            stackView.widthAnchor.constraint(equalTo: self.widthAnchor)
        ])
    }
    private func setupColorViews() {
        let colorViews: [UIView] = [redView,orangeView,yellowView,greenView,blueView]
        colorViews.forEach { view in
            stackView.addArrangedSubview(view)
        }
    }
}
```

「これでできるはず...」

```swifrt
PlaygroundPage.current.liveView = View()
```

![スクリーンショット 2021-05-04 10.15.35.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/0fb0bf09-9300-2cd8-6814-0c460fc47ce1.jpeg)


「おーできた」

## fillProportionallyらしいコーディングをしてみる

「widthの実際の値をもとにした比に基づいてwidthが定まるのがfillProportionallyだった」

```swift
let redView = ColorView(color: .red, size: CGSize(width: 1, height: 100))
let orangeView = ColorView(color: .orange, size: CGSize(width: 2, height: 100))
let yellowView = ColorView(color: .yellow, size: CGSize(width: 3, height: 100))
let greenView = ColorView(color: .green, size: CGSize(width: 4, height: 100))
let blueView = ColorView(color: .blue, size: CGSize(width: 5, height: 100))
```

なら、これは1:2:3:4:5になるはず

![スクリーンショット 2021-05-04 10.14.34.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/0ac98e6c-d08b-c955-ab1e-055a97f638e5.jpeg)


「おー、なってるやん。fillPortionally完全に理解できたわ！」

## ソースコード

Playgrounds上でコピペで動きます。

```swift
import PlaygroundSupport
import UIKit

class ColorView: UIView {
    var size: CGSize
    init(color: UIColor, size: CGSize) {
        self.size = size
        super.init(frame: CGRect())
        backgroundColor = color
        
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    override var intrinsicContentSize: CGSize {
        return CGSize(width: size.width, height: size.height)
    }
}

class View: UIView {
    let stackView = UIStackView()
    let redView = ColorView(color: .red, size: CGSize(width: 1, height: 100))
    let orangeView = ColorView(color: .orange, size: CGSize(width: 2, height: 100))
    let yellowView = ColorView(color: .yellow, size: CGSize(width: 3, height: 100))
    let greenView = ColorView(color: .green, size: CGSize(width: 4, height: 100))
    let blueView = ColorView(color: .blue, size: CGSize(width: 5, height: 100))
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .white
        setupStackView()
        setupColorViews()
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    private func setupStackView() {
        stackView.backgroundColor = .white
        stackView.axis = .horizontal
        stackView.alignment = .fill
        stackView.distribution = .fillProportionally
        addSubview(stackView)
        stackView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            stackView.centerYAnchor.constraint(equalTo: self.centerYAnchor),
            stackView.centerXAnchor.constraint(equalTo: self.centerXAnchor),
            stackView.widthAnchor.constraint(equalTo: self.widthAnchor)
        ])
    }
    private func setupColorViews() {
        let colorViews: [UIView] = [redView,orangeView,yellowView,greenView,blueView]
        colorViews.forEach { view in
            stackView.addArrangedSubview(view)
        }
    }
}

PlaygroundPage.current.liveView = View()
```