StackViewの挙動を確認した話

## 準備
<details><summary>色と大きさを指定できるUIViewを作成</summary><div>


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

</div></details>

<details><summary>表示するViewを作成。この中で先程の`ColorView`を使っている。</summary><div>


```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 = .gray
        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)
        }
    }
}
```
</div></details>


## axis

これは理解しやすい。積み上げる方向が水平方向か鉛直方向か指定できる。

<details><summary>UIKit.UIStackViewでの定義</summary><div>



```swift
extension NSLayoutConstraint {

    public enum Axis : Int {

        case horizontal = 0

        case vertical = 1
    }
}
```

</div></details>

## alignment

alignmentは名詞で一列、整列線といった意味。方向を定めた後にどこに並ぶのかを決める。

<details><summary>UIKit.UIStackViewでの定義</summary><div>



```swift
public enum Alignment : Int {

        
        /* Align the leading and trailing edges of vertically stacked items
         or the top and bottom edges of horizontally stacked items tightly to the container.
         */
        case fill = 0

        
        /* Align the leading edges of vertically stacked items
         or the top edges of horizontally stacked items tightly to the relevant edge
         of the container
         */
        case leading = 1

        public static var top: UIStackView.Alignment { get }

        case firstBaseline = 2 // Valid for horizontal axis only

        
        /* Center the items in a vertical stack horizontally
         or the items in a horizontal stack vertically
         */
        case center = 3

        
        /* Align the trailing edges of vertically stacked items
         or the bottom edges of horizontally stacked items tightly to the relevant
         edge of the container
         */
        case trailing = 4

        public static var bottom: UIStackView.Alignment { get }

        case lastBaseline = 5 // Valid for horizontal axis only
    }
```



</div></details>

### fill
fillは高さが揃うようにaxisと垂直方向の辺の長さを広げる。
中身のViewが持っていた高さの制約よりも優先される。

![スクリーンショット 2021-05-05 16.49.17.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/9e897f65-037e-f159-86ad-108cb8e9e739.jpeg)

### leading/top
axisがhorizontalのt時はtop、verticalの時はleadingが適切であるよう(Storyboardだと表示されなくなる)だが、どちらでも動く。中身のUIViewの上の辺(`topAnchor`)がx,y座標の小さい方の上の辺に揃う。


![スクリーンショット 2021-05-05 15.29.06.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/4fc1b4c7-e9ac-06df-b07e-e0a73e340cf5.jpeg)

### firstBaseLine
StackViewにUILabelが含まれている時、その1行目に高さが揃う。
<details><summary>なお、firstBaseLineとlastBaseLineで用いているコードは他のものと異なっている。</summary><div>

```swift
import PlaygroundSupport
import UIKit
class ColorView: UIView {
    let 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 label = UILabel()
    let redView = ColorView(color: .red, size: CGSize(width: 100, height: 20))
    let orangeView = ColorView(color: .orange, size: CGSize(width: 100, height: 40))
    let yelloView = ColorView(color: .yellow, size: CGSize(width: 100, height: 60))
    
    let stackView = UIStackView()
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .white
        addSubViewsConstrains()
        setupStackView()
        setupLabel()
    }
    private func addSubViewsConstrains() {
        //StackView
        addSubview(stackView)
        stackView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            stackView.centerXAnchor.constraint(equalTo: self.centerXAnchor),
            stackView.centerYAnchor.constraint(equalTo: self.centerYAnchor),
            stackView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.9),
            stackView.heightAnchor.constraint(equalToConstant: 500)
        ])
        //Label
        addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            label.centerYAnchor.constraint(equalTo: stackView.centerYAnchor)
        ])
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    private func setupLabel() {
        label.backgroundColor = .white
        label.numberOfLines = 5
        label.text = """
            first BaseLine
            second BaseLine
            third BaseLine
            4th BaseLine
            last BaseLine
            """
    }
    private func setupStackView() {
        stackView.backgroundColor = .gray
        stackView.axis = .horizontal
        stackView.alignment = .lastBaseline
        stackView.distribution = .fillEqually
        let views: [UIView] = [redView,label,orangeView,yelloView]
        views.forEach { view in 
            stackView.add![スクリーンショット 2021-05-05 15.19.14.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/68321e3f-f289-6d9d-14a8-55a1761c3919.jpeg)
ArrangedSubview(view)
        }
    }
    
}
PlaygroundPage.current.liveView = View()
```

</div></details>

![スクリーンショット 2021-05-05 15.15.27.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/90cf234a-b844-a08c-1548-dc4c367841b9.jpeg)

### center


StackViewの鉛直中央(`centerYAnchor`)に中身の鉛直中央が揃うように配置される。

![スクリーンショット 2021-05-04 11.15.34.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/6914f5cf-289a-49a4-5ae9-46dc42d9781c.jpeg)

### trailing/bottom
leading/topの逆ver。

![スクリーンショット 2021-05-05 15.43.04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/30a19c5f-4106-d0eb-ab0e-bccebc3b74d7.jpeg)

### lastBaseLine
StackViewにUILabelが含まれている時、その最後の行に高さが揃う。

![スクリーンショット 2021-05-05 15.19.14.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/4d3018f9-6625-682e-61a4-590dce524061.jpeg)

## destribution
destributionは名詞で配分や散布という意味。axisで定めた方向に対して、中身のUIViewをどのように広げるのかを定めるのがdestributionであるといえる。

<details><summary>UIKit.UIStackViewでの定義</summary><div>

```swift
public enum Distribution : Int {

        
        case fill = 0

        
        case fillEqually = 1

        
        case fillProportionally = 2

        
        case equalSpacing = 3

        
        case equalCentering = 4
    }
```
</div></details>
### fill

StackViewのaxis方向の幅(horizontalであればwidth、verticalであればheight)がいっぱいになるまでUIViewを広げる。中身の辺の幅が足りない時にどのUIViewの幅が伸ばされるのかということについては調査中であるが、[UILayoutPriority](https://developer.apple.com/documentation/uikit/nslayoutconstraint/1526946-priority)あたりが絡んでいそう。画像では最初に追加した`redView`のwidthが伸ばされている。

![スクリーンショット 2021-05-05 16.44.25.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/ef8dbc77-4e8c-7d95-b2c4-f4406b280e20.jpeg)


<details><summary>なお、destributionで説明する画像の中でalignmentはcenterにしてある。</summary><div>

```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: 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 = .gray
        setupStackView()
        setupColorViews()
        
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    private func setupStackView() {
        stackView.backgroundColor = .white
        stackView.axis = .horizontal
        stackView.alignment = .center
        stackView.distribution = .fill
        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()
```

</div></details>


### fillEqualy
StackViewのaxis方向の幅(horizontalであればwidth、verticalであればheight)がいっぱいになるまで中身のViewを均等に広げる。

![スクリーンショット 2021-05-05 16.44.08.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/d93664bc-6075-42a4-f622-fea1411d5ecb.jpeg)

### fillProportionally
中身のUIViewの幅の和がStackViewの幅に満たない時、中身のUIViewの幅の比をもとに各UIViewを広げる。
これは各UIViewのwidthを1,2,3,4,5にしているので、stackViewのwidthを1:2:3:4:5に分割するように広げられる。

```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))
```
![スクリーンショット 2021-05-05 16.52.09.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/389e1c6e-fbe3-6ffd-6edf-aa45fea296d6.jpeg)

### equalSpacing
中身のUIViewの間隔が等しくなるように配置する。

![スクリーンショット 2021-05-05 16.59.04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/d8e144c9-d7e0-4db5-422a-21b45932c718.jpeg)


### equalCentering
中身のUIViewの中心の間隔が等しくなるように配置される。
![スクリーンショット 2021-05-05 16.59.24.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/3a19c879-cb3f-4632-670f-65ca64685d85.jpeg)

## ソースコード
コピペで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: 60, height: 100))
    let orangeView = ColorView(color: .orange, size: CGSize(width: 90, height: 100))
    let yellowView = ColorView(color: .yellow, size: CGSize(width: 120, height: 100))
    let greenView = ColorView(color: .green, size: CGSize(width: 150, height: 100))
    let blueView = ColorView(color: .blue, size: CGSize(width: 180, height: 100))
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .gray
        setupStackView()
        setupColorViews()
        
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    private func setupStackView() {
        stackView.backgroundColor = .white
        stackView.axis = .horizontal
        stackView.alignment = .center
        stackView.distribution = .equalCentering
        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()

```