iOS

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

More than 3 years have passed since last update.


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:



  • レイアウト変更という点だけ考慮すれば良い -> 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