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でも触れられています。解説記事を見るよりも情報量は多いので、是非見てみてください。
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:
- iPhoneのみ対応するアプリケーションか ->
- レイアウト変更という点だけ考慮すれば良い ->
updateViewConstraints
等
アプリケーションで実現したいことに応じて、どうハンドリングするべきかは変わります。
サンプルやネットで見つけた記事を鵜呑みにせず、自分の頭で考えましょう。
その他deprecated化されたもの
上記で上げたもの以外にも、UIBarMetrics
の中にdeprecated化されたものがあります。
UIBarMetricsLandscapePhone
UIBarMetricsLandscapePhonePrompt
新しく追加された以下の項目に置換えましょう。
UIBarMetricsCompact
UIBarMetricsCompactPrompt
詳しくは、UIBarCommon.h
を参照してください。
気になること
iOS7/8 両方の環境で動作するアプリの場合の対策
iOS7/8の両方の環境に対応させる場合は、それぞれのバージョンに対応させるため
willRotateToInterfaceOrientation: duration:
-
viewWillTransitionToSize: withTransitionCoordinator:
等のメソッドをオーバーライドしておくと良いです。
そのうえで、共通化できる処理は別メソッドに切り出すなどしておけば整理できます。
以下の例はwillRotateToInterfaceOrientation: duration:
で回転をハンドリングしていたUIViewControllerのサブクラスをiOS8に対応させるため、viewWillTransitionToSize: withTransitionCoordinator:
のオーバーライドを追加した例です。
- (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
Others
Programming iOS 8 - Dive Deep into Views, View Controllers, and Frameworks