[iOS 8] マルチデバイス対応の新機能「Trait Collection」

iOS8
150件のシェア(すこし話題の記事)

Adaptive なアプリを作ろう

iPhone 6 / iWatch が発表され、iOS に新しい画面サイズのデバイスが仲間入りしました。おそらく今後も新しい画面サイズの iOS デバイスが登場してくるでしょう。このことから、各画面サイズに応じたアプリにすることはもはや必須要件と言えるでしょう。

このことはもちろん Apple も考慮しており、iOS 8 から Adaptive という概念が導入されました。Adaptive とは適応性という意味です。つまり画面サイズが小さくても大きくても、それぞれの画面サイズにうまく適応させて設計していきましょうという考えかたです。

ということで、本稿では iOS 8 からのマルチサイズに適応させる方法について解説します。

Size Class

これまで iPhone / iPad の判定や Portrait / Landscape の判定には UIUserInterfaceIdiomUIInterfaceOrientation を利用していましたが、これらは非推奨になりました。iOS 8 からは Size Class を利用します。

Size Class はデバイスのサイズを判別するための新しい定義です。各画面サイズを Regular サイズCompact サイズ の2つに大きく分け、それぞれのサイズによって画面構成を変えるようになっています。

ここで、これまでの画面サイズごとの振る舞いを思い出してみましょう。iPad は画面サイズが比較的広いので、Split View Controller による2画面構成や Form Sheet、Pop Over などさまざまな見た目で表示できます。しかし iPhone など画面サイズが比較的小さいデバイスでは表示しきれないので、いずれも単純な View Controller で表現されてしまいます。

上記の問題を、iOS 8 では iPhone (Compact) は iPad (Regular) の一部と見なすことで解決しています。例えば Split View Controller の場合、iPad では Master-Detail を1画面で表示していましたが、iPhone では Master-Detail の一部として Master を表示していることになります。

Size Class は以上のような概念に基づいて、Regular / Compact を Vertical / Horizontal でそれぞれ定義しています。

Regular (Horizontal) Compact (Horizontal)
Regular (Vertical)
Compact (Vertical)

1つの View Controller で考えた場合、iPad は Vertical、Horizontal いずれも Regular になります。いっぽう iPhone 5s および iPhone 6 は Portrait の場合は Vertical が Regular、Horizontal が Compact になります。Landscape の場合はいずれも Compact になります。

Portrait - Width Portrait - Height Landscape - Width Landscape - Height
iPhone 5s
iPhone 6
Compact Regular Compact Compact
iPhone 6 Plus Compact Regular Regular Compact
iPad Regular Regular Regular Regular

Split View Controller の場合は少し特殊で、iPad の場合 Master 側の View Controller は Horizontal が Compact になります。

UITraitCollection とは?

上記で解説した Size Class を始めとしたデバイスの特徴的な情報は UITraitCollection クラスでまとめられています。UITraitCollection は次の4つのプロパティを持ちます。

プロパティ名 説明
horizontalSizeClass UIUserInterfaceSizeClass Horizontal の Size Class
verticalSizeClass UIUserInterfaceSizeClass Vertical の Size Class
userInterfaceIdiom UIUserInterfaceIdiom デバイスの種類
displayScale CGFloat 画面倍率 (Retinaは2.0)

デバイスの種類や向きなどを判定したい場合は、View Controller から取得した UITraitCollection オブジェクトと自分で生成した UITraitCollection オブジェクトを比較します。

また、UITraitCollection は5つのコンビニエンスコンストラクタから生成することができます。各プロパティを引数にセットできるほか、既存の UITraitCollection オブジェクトの配列から生成できるコンビニエンスコンストラクタもあります(Compact と Regular が混合している場合は Regular が優先的にセットされます)。

  • traitCollectionWithDisplayScale:
  • traitCollectionWithTraitsFromCollections:
  • traitCollectionWithUserInterfaceIdiom:
  • traitCollectionWithHorizontalSizeClass:
  • traitCollectionWithVerticalSizeClass:

UITraitCollection を使う

UITraitCollection を取得する

ということで UITraitCollection を使ってみましょう。UITraitCollection オブジェクトを取得できるのは UITraitEnvironment プロトコルを実装しているクラスになります。代表的なのは UIViewController です。このプロトコルの traitCollection プロパティが現在の UITraitCollection です。

override func viewDidLoad() {
    super.viewDidLoad()
    println("trait collection : \(traitCollection)")
}

なお、デバイスの向きの変更などで UITraitCollection が変更されたときに traitCollectionDidChange: が呼び出されます。

override func traitCollectionDidChange(previousTraitCollection: UITraitCollection!) {
    println(__FUNCTION__);
}

previousTraitCollection は直前に適用されていた UITraitCollection オブジェクトです。直前の UITraitCollection と現在の UITraitCollection の比較なども簡単に実装できますね。

UITraitCollection を判定する

View Controller などから UITraitCollection のオブジェクトが取得できたら、あとは条件を作って処理を分けていけば良いです。次の例では「Horizontal Size が Regular かどうか」を判定しています。

// 条件となるUITraitCollection
let collection = UITraitCollection(horizontalSizeClass: .Regular)
// 含有しているか判定
if traitCollection.containsTraitsInCollection(collection) {
    println("Horizontal Size = Regular")
} else {
    println("Horizontal Size = Compact")
}

判定は、呼び出し元の View Controller から取得した現在の UITraitCollection オブジェクトのcontainsTraitsInCollection を使って行います。引数には条件となる UITraitCollection オブジェクトを渡すことで、2つの オブジェクトを比較することができます。

まとめ

Size Class ならびに Trait Collection はデバイスの種類や特性の違いをシンプルかつ拡張性が高い形でまとめている、まさに Adaptive な概念だと思います。アプリ側で対応しておけば、今後出てくるであろう新しいデバイスへの対応もきっと簡単になるでしょう。iOS 8 対応アプリの開発の中では絶対に必要な知識だと思うので、ぜひ覚えておきましょう。

参考

  • IT_oh

    質問させていただきます。
    iOS8.1で画面回転した時にiPhoneではtraitCollectionDidChange は呼ばれますが、iPadでは呼ばれません。
    回転後のtraitCollectionDidChange で行いたい処理がiPadで出来ず、困っております。
    このような現象はそちらの方で確認されていますでしょうか?
    なお、viewWillTransitionToSize は両方とも呼ばれます。

    • suwa.yuki

      ご質問、ありがとうございます。

      traitCollectionDidChangeはUITraitCollectionが変更されたときに呼び出されるメソッドです。
      iPadの場合、WidthとHeightはいずれもRegularですので、
      回転時にUITraitCollectionは変更されません。
      したがって、iPadの場合は回転時にtraitCollectionDidChangeが呼ばれません。

      iPadの回転時に何らかの処理を加えたい場合は、
      viewWillTransitionToSizeの中で処理を行う必要があるようです。

      • IT_oh

        ご回答ありがとうございます。

        -(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id )coordinator
        {
        [coordinator animateAlongsideTransition: ^(id context) {

        // 回転中の処理

        } completion: ^(id context) {

        // 回転後の処理
        }];
        }
        [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
        }

        のように書けば良さそうです。