iOS8以降で、どうデバイスの回転を取り扱うかまとめてみる

# iOS8でデバイスの向きに対する扱いが変わった
iOS8以降、Adaptive UIやSize Class、Trait Collection等の新しい概念が導入され、レイアウトへの考え方が大きく変わりました。それに伴い、デバイスの向きに対する考え方も大きく変わっています。

## Device Orientation -> Size Class
iOS7以前は、Landscape/Portraitが変更されたことは、画面の向きが変わったという意味でハンドリングされてきました。
iOS8以降、Landscape/Portraitの変更は、**画面がリサイズされた(= Traitが変更された)** という意味で取り扱われます。

WWDC2014の[Building Adaptive Apps with UIKit](http://devstreaming.apple.com/videos/wwdc/2014/216xxcnxc6wnkf3/216/216_building_adaptive_apps_with_uikit.pdf?dl=1)でも触れられています。解説記事を見るよりも情報量は多いので、是非見てみてください。

# Rotation -> Size Class Resizing
こうした変更により、デバイスの回転は **Traitの変更に応じてどのようにリサイズするか** と変えていく必要があります。

## Deprecated化されたメソッドなど
iOS8からは、```UIViewController```のメソッドの中で、デバイスの回転を取り扱うものがdeprected化されています。

* ```willRotateToInterfaceOrientation: duration:```
* ```didRotateFromInterfaceOrientation:```

## レイアウトの変更をハンドルするメソッド
レイアウトの変更は以下のようなメソッドでハンドリングすることができます。

### ```willTransitionToTraitCollection:withTransitionCoordinator:```
```willTransitionToTraitCollection:withTransitionCoordinator:```は、**デバイスの回転に伴ってTrait Collectionが変更される時**に呼び出されます。
このため、以下のような場合には呼び出されません。

* iPadでデバイスを回転する場合
* iPhoneで180度デバイスを回転する場合

このメソッドをオーバーライドする場合は、*super*を呼び出してください。

### ```viewWillTransitionToSize: withTransitionCoordinator:```
```viewWillTransitionToSize: withTransitionCoordinator:```は、**デバイスが回転する時** に呼び出されます。
Trait Collectionの変更に関わらず呼び出されるので、以下のような場合でも呼びだされます。

* iPadでデバイスを回転する場合
* iPhoneで180度デバイスを回転する場合

このメソッドをオーバーライドする場合は、*super*を呼び出してください。

なお、このメソッドはデバイスの回転に特化したメソッドですので、回転に関連しないレイアウト変更をハンドリングしたい場合は```viewWillLayoutSubviews```を使ってください。

### ```traitCollectionDidChange:```
**Trait Collectionが変更された後** に呼び出されます。
加えて、アプリケーションの起動時や、ViewControllerが初めてロードされたタイミングでも呼び出されます。

### これ以外のメソッド
デバイスの回転がリサイズに置き換わりますので、レイアウトの変更に関連するメソッドも候補に挙げられます。
* ```updateViewConstraints```
* ```viewWillLayoutSubviews```
* ```viewDidLayoutSubviews```

### メソッド置き換えの指針
```willRotateToInterfaceOrientation: duration:```や ```didRotateFromInterfaceOrientation:```を置き換えるときは、以下のような基準を元に考えれば良いと思います。

* デバイスの回転のみ考慮すれば良い -> ```viewWillTransitionToSize: withTransitionCoordinator:```
* デバイスの回転時に、Trait Collectionの変更も考慮する必要がある
  * iPhoneのみ対応するアプリケーションか -> ```willTransitionToTraitCollection:withTransitionCoordinator:```
  * iPhone/iPadの両方に対応するアプリケーションか -> ```viewWillTransitionToSize: withTransitionCoordinator:```
* レイアウト変更という点だけ考慮すれば良い -> ```updateViewConstraints```等

アプリケーションで実現したいことに応じて、どうハンドリングするべきかは変わります。
サンプルやネットで見つけた記事を鵜呑みにせず、自分の頭で考えましょう。



## その他deprecated化されたもの
上記で上げたもの以外にも、```UIBarMetrics```の中にdeprecated化されたものがあります。

* ```UIBarMetricsLandscapePhone```
* ```UIBarMetricsLandscapePhonePrompt```

新しく追加された以下の項目に置換えましょう。

* ```UIBarMetricsCompact```
* ```UIBarMetricsCompactPrompt```

詳しくは、```UIBarCommon.h```を参照してください。

# 気になること
## iOS7/8 両方の環境で動作するアプリの場合の対策
iOS7/8の両方の環境に対応させる場合は、それぞれのバージョンに対応させるため
* ```willRotateToInterfaceOrientation: duration:```
* ```viewWillTransitionToSize: withTransitionCoordinator:```
等のメソッドをオーバーライドしておくと良いです。
そのうえで、共通化できる処理は別メソッドに切り出すなどしておけば整理できます。

以下の例は```willRotateToInterfaceOrientation: duration:```で回転をハンドリングしていたUIViewControllerのサブクラスをiOS8に対応させるため、```viewWillTransitionToSize: withTransitionCoordinator:```のオーバーライドを追加した例です。

```obj-c
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    // iOS7.xでのみ呼び出される
    [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
    if ([[UIApplication sharedApplication] statusBarOrientation] != toInterfaceOrientation) {
        [self methodToLayoutChange];
	}
}

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
    // iOS8.xで呼び出される
    [super traitCollectionDidChange:previousTraitCollection];
    if ((self.traitCollection.verticalSizeClass != previousTraitCollection.verticalSizeClass)
        || (self.traitCollection.horizontalSizeClass != previousTraitCollection.horizontalSizeClass)) {
        [self methodToLayoutChange];
    }
}

- (void)methodToLayoutChange
{
    // 共通で実施できる何かしらの処理
}
```

同じような処理を行うメソッドを複数定義していることになり、据わりが悪い印象を受けますが、iOS7のサポート終了時に```willRotateToInterfaceOrientation: duration:```を削除してしまうだけで済みます。


# 参考にしたURL
## WWDC 2014
[Building Adaptive Apps with UIKit](http://devstreaming.apple.com/videos/wwdc/2014/216xxcnxc6wnkf3/216/216_building_adaptive_apps_with_uikit.pdf?dl=1)

## Others
[Programming iOS 8 - Dive Deep into Views, View Controllers, and Frameworks](http://shop.oreilly.com/product/0636920034261.do)