【Swift】UIStackViewを使って回転時のレイアウト変更を楽にする

# 導入
今回は、アプリの向きを縦・横で切替える時に、レイアウト変更を行う場合に便利なTipsを書いていきます。
縦画面と横画面でレイアウトを変えたいという事はよくあると思いますが、その度に制約の切替をたくさん行うのは面倒くさいし、思わぬレイアウト崩れの原因にもなるので、できれば制約の変更は最小限にしておきたいところです。

**そこで、今回は`UIStackView`を使って縦・横のレイアウト切替を楽に終わらせる実装を記事にしました。**

この記事を書こうと思ったのも、自分が上記のような実装をする時に、最初は制約の変更でゴリ押ししようとしていましたが、「`UIStackView`が使える！」と途中で気づいたためです。

そんなわけで、読んでいただいた方の参考になればと思います。

# やりたい事
五十音の「あいうえお」ラベルを画面の縦横の向きによって、「横並び」or「縦並び」を切替えます。

**縦レイアウトの場合**
<img width="280" alt="portrate.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/580445/c7dc1e83-de4a-9404-f6a2-8726ad0d57c5.png">
**横レイアウトの場合**
<img width="480" alt="landscape.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/580445/cd2db439-246e-e29a-31a7-efa4e5f0c94b.png">

このようなレイアウト変更を`UIStackView`を使って、なるべく楽に実装していきます。
おそらくこのようなレイアウト変更を普通に制約の変更だけでゴリ押しするとかなり面倒くさいと思います。

# 実装
`UIStackView`についての詳しい解説は、この記事では省略させていただきますが、Qiitaや技術ブログでわかりやすい記事が沢山あるので、`UIStackView`を使ったことがない方は、ググってみていただければと思います。

### StoryBoard側の設定

#### UIのイメージ
縦の場合を想定
![layout_sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/580445/33729ea9-7781-6f36-9151-2d9ab52f52a5.png)

アプリの縦横を替えた時に、StackViewそのもののレイアウトを変更したいので、高さと幅の制約は、優先度を900と750に設定して動的に切り替えるようにしています。

#### StackViewの設定
<img width="240" alt="landscape.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/580445/a441f39e-5b03-4400-95e6-6641c8e32b5f.png">


### ソースコード
StackView自体のレイアウト変更は、制約を変更することで実現しています。

```swift

import UIKit

class StackViewPreview: UIViewController {

    @IBOutlet weak var contentView: UIView!
    @IBOutlet weak var stackView: UIStackView!
    @IBOutlet weak var stackViewLeadingConst: NSLayoutConstraint! // StackViewの先頭を親ビューの先頭の制約 
    @IBOutlet weak var stackViewBottomConst: NSLayoutConstraint!  // StackViewのボトムと親ビューのボトムの制約
    @IBOutlet weak var stackViewHeightConst: NSLayoutConstraint!  // StackViewの高さ
    @IBOutlet weak var stackViewWidthConst: NSLayoutConstraint!   // StackViewの横幅
    
    // アプリ（画面）の向きを判断
    var isPortrait: Bool {
        return UIApplication.shared.windows.first?.windowScene?.interfaceOrientation.isPortrait ?? true
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func viewWillAppear(_ animated: Bool) {
        updateConstraint()
    }
    
    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        coordinator.animate(alongsideTransition: { _ in
            self.updateConstraint()
        }, completion: nil)
    }
    
    func updateConstraint() {
        if isPortrait {
            // StackViewが横長になるように、制約を切替
            stackViewBottomConst.isActive = false
            stackViewLeadingConst.isActive = true
            // stackViewの高さの優先度を上げて、高さを有効。横幅を無効。
            stackViewHeightConst.priority = UILayoutPriority(900)
            stackViewWidthConst.priority = UILayoutPriority(750)
            // ★ Point ★
            stackView.axis = .horizontal
        } else {
            // StackViewが縦長になるように、制約を切替
            stackViewLeadingConst.isActive = false
            stackViewBottomConst.isActive = true
            // stackViewの横幅の優先度を上げて、高さを無効。横幅を有効。
            stackViewHeightConst.priority = UILayoutPriority(750)
            stackViewWidthConst.priority = UILayoutPriority(900)
            // ★ Point ★
            stackView.axis = .vertical
        }
    }
}
```

制約によるレイアウトの変更は、今回の記事の目的とは異なるので、割愛します。

ここでは、★Point★とコメントしたところについて解説します。
といっても、`UIStackView`について理解している方であれば、ソースコードの意味については、説明不要かもしれませんが、
アプリの縦横が切り替わる瞬間に、StackViewの`axis`も同時に切替えて、X軸方向に並べる（横並び）か、Y軸方向に並べるかを（縦並び）を指定しています。

**どちらかといえば、こういったレイアウト変更時には、`UIStackView`が使えるというアイディアの部分が伝わればと思います。**