トレイトコレクションとレイアウト

趣味でiOSアプリ開発をかじっていた自分が、改めてiOS開発を勉強し始めた際に、曖昧に理解していたところや知らなかったところをメモしています。いつか書き直します。

## 参考文献

この記事は以下の書籍の情報を参考にして執筆しました。

- [よくわかるAuto Layout iOSレスポンシブデザインをマスター](https://www.amazon.co.jp/%E3%82%88%E3%81%8F%E3%82%8F%E3%81%8B%E3%82%8BAuto-Layout-iOS%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B7%E3%83%96%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%82%92%E3%83%9E%E3%82%B9%E3%82%BF%E3%83%BC-%E5%B7%9D%E9%82%89-%E9%9B%84%E4%BB%8B/dp/4865940324/)

#トレイトコレクション
トレイトコレクションは表示に関わるアプリの状態管理をしていて、レイアウトを決定するための情報を提供してくれる。
トレイトコレクションの状態に合わせてレイアウトを変更することで柔軟なレイアウトを作れる。
トレイトコレクションはスクリーンで定義されウィンドウからビューコントローラに渡されていく。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542614/6a0cc25f-b06b-9b0e-9084-80047089c71f.png)

##UITraitCollectionのプロパティ

|プロパティ|型|役割|値
|---|---|---|---|
displayScale|CGFlat|表示スケール|非Retina:1.0, Retina:2.0, 指定なし:0.0
horizontalSizeClass|UIUserInterfaceSizeClass|水平方向の抽象的なサイズ|Unspecified, Compact, Regular
verticalSizeClass|UIUserInterfaceSizeClass|垂直方向の抽象的なサイズ|Unspecified, Compact, Regular
userInterfaceIdiom|UIUserInterfaceIdiom|端末種別|Unspecified, Phone, Pad, TV
forceTouchCapability|UIForceTouchCapability|3Dタッチフラグ|Unknown, Unavailable, Availavle

####サイズクラス

|端末と向き|高さ|幅|
|---|---|---|
iPad縦|Regular|Regular
iPad横|Regular|Regular
iPhone縦|Regular|Compact
iPhone横|Compact|Compact
iPhonePlus縦|Regular|Compact
iPhonePlus横|Compact|Regular

##UITraitCollectionを比較する
containsTraits(: )メソッドを呼び出して比較する。
引数のトレイトコレクションを含んでいればtrueを返す。

```swift
    // HorizontalSizeClass = Regular という値のみを持つトレイトコレクションを作成
    let regularWidthTrait = UITraitCollection(horizontalSizeClass: .compact)
    // self.traitCollection.HorizontalSizeClass == Compact の時trueを返す
    print(self.traitCollection.containsTraits(in: regularWidthTrait))    // true
```

##UITraitCollectionを合成する
init(traitsFrom: )を用いて、トレイトコレクッションの配列を合成したトレイトコレクションを生成できる。
下記の例ではビューコントローラーの持つトレイトコレクションのhorizontalSizeClassをregularに上書きしたトレイトコレクションを生成する例。

```swift
    let regularWidthTrait = UITraitCollection(horizontalSizeClass: .regular)
    let overrideTraitCollection = UITraitCollection(traitsFrom: [self.traitCollection, regularWidthTrait])
```

##端末の回転挙動を扱う
端末の回転挙動は以下の４つのステップに分かれている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542614/6f922816-29dd-a779-aa66-7786736ad01b.png)

最初のセットアップ時にUITraitEnvironmentプロトコルとUIContentContainerプロトコルで定義されたメソッドたちが呼び出され、これらのメソッド内で回転挙動を扱うことができる

####willTransition
トレイトコレクションが変化する直前かつ端末の回転時サイズクラスが変化する時に呼び出される。
サイズが変化しない場合、180°回転時やiPadのように縦横のsizeが同じ場合は呼ばれない。
トレイトコレクションはボトムアップに変更されていくので、トレイトコレクションの変更を子ビューコントローラに反映させる為にオーバーライドするときはsuperを呼ぶ必要がある。

####viewWillTransition
アプリを表示しているウィンドウの大きさが変化する直前に呼ばれる。
トレイトコレクションの変更に関係せず表示画面サイズが変更されるときに呼び出されるので180°回転時やiPadでも呼び出される。
このメソッドも変更を子ビューコントローラに反映させる為にオーバーライドするときはsuperを呼ぶ必要がある。


####taritCollectionDidChange
トレイトコレクションが変化した直後またトレイトコレクションがセットされた後に呼び出される。
アプリ起動時やビューコントローラが初めてロードされた時も呼び出される。
このメソッドも変更を子ビューコントローラに反映させる為にオーバーライドするときはsuperを呼ぶ必要がある。

##端末回転時のアニメーション
端末回転時にレイアウトを変更するだけなら上記３つのどのメソッド内に処理を書いても問題はないが、アニメーションと同期してオブジェクトを動かす場合はwillTransition,viewWillTransitionを使う。

```swift
  override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)
    coordinator.animate(alongsideTransition: { context in
      if (size.width <= size.height){
        // 縦画面レイアウト用にアニメーションさせたい処理
        print("tate")
      } else {
        // 横画面レイアウト用にアニメーションさせたい処理
        print("yoko")
      }
    }, completion: nil)
  }
```

##各オブジェクトごとにサイズクラスを適用する
下記のように設定するとWidthがRegular、HeightがCompactの時のみオブジェクトが適用される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542614/dcf21557-e071-034c-7670-61a1371c029f.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542614/d90e0c05-a7a5-9ccc-7fb9-d43c87b8f216.png)