Xcode 6 時代のマルチデバイス対応 ～Size Classとベクター画像～

Xcode 6 の新しい仕組みである Size Class を使い、

- iPad (Universal)
- iPhone 6 (4.7 inch)
- iPhone 6 Plus (5.5 inch)

の複数画面サポートを行なった際の断片的なメモ集です。

同じく Xcode 6 から **Asset Catalog でベクター形式がサポート** されるようになったので、こちらについてわかったこと等も書いてあります。


<span class="deco" style="font-size:small;">※ちなみに Size Class やベクター形式画像の使用は <b>iOS 8 以上縛りではありません</b>。単に Xcode 6 の新機能というだけなので。</span>


#Size Class

##前提知識をつける

###Size Classsとは

クラスメソッドさんの下記記事が日本語、図解入りでとてもわかりやすいです。

- [マルチデバイス対応の新機能「Trait Collection」](http://dev.classmethod.jp/references/ios8-trait-collection/)


###Size Class 機能を使って複数画面サイズ対応する手順のチュートリアル動画

英語ですが、<b>音声なしでもわかりやすい</b>です。Xcodeのプレビュー機能を駆使するのめちゃめちゃ大事。

https://www.youtube.com/watch?v=p5wD8dvSDbM

<object width="560" height="315"><param name="movie" value="//www.youtube.com/v/p5wD8dvSDbM?hl=ja_JP&amp;version=3&amp;rel=0"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="//www.youtube.com/v/p5wD8dvSDbM?hl=ja_JP&amp;version=3&amp;rel=0" type="application/x-shockwave-flash" width="560" height="315" allowscriptaccess="always" allowfullscreen="true"></embed></object>


##シーン別 Size Class の指定方法

###AutoLayout を Size Class ごとに指定する

もっとも基本的な Size Class の用法。IB の Size Inspector で設定します。


<img src="http://f.st-hatena.com/images/fotolife/s/shu223/20141002/20141002091213.jpg" width="300">


###UIコンポーネントを Size Class ごとに指定する

後述しますが、ある UIコンポーネントの一部プロパティだけ Size Class ごとに変えたい、UICollectionView のレイアウトだけ変えたい、ってなると、そのコンポーネントのオブジェクト自体を Size Class ごとに使い分けることになります。


で、こちらは IB の Attributes Inspector で設定します（わかりにくい！）


<img src="http://f.st-hatena.com/images/fotolife/s/shu223/20141002/20141002091234.jpg" width="300">


ちなみに、IBAction は困らないのですが、<b>IBOutlet は IB 上の1つのオブジェクトとしか接続できない</b>のでちょっと困りました。僕は結局コードから subviews をたどって取得するようにしましたが、もっといい方法があるかもしれないです。


###UIコンポーネントのプロパティを Size Class ごとに指定する

たとえば AutoLayout で iPad 用のとある UIButton のサイズを大きめに設定したとすると、その <b>titleLabel のフォントも大きくしたい</b> ということになってきます。


で、これを IB 上でプレビューしながら設定したい、となると結局、上記の「UIコンポーネントを Size Class ごとに指定する」で対応するしかないかと。


###UICollectionView のレイアウトを Size Class ごとに指定する

iPad、全然サイズ感違うので、UICollectionView の UICollectionViewFlowLayout （もしくは、UICollectionViewLayout を継承する何らかのレイアウト）を別々に指定したい、ってことになると思います。


で、結論から言うと、自分は <b>UICollectionView を Size Class ごとにつくる</b> ことで対応しました。上述の「UIコンポーネントを Size Class ごとに指定する」方法です。Storyboard の該当 CollectionView 上でカスタムセルを定義していた場合は、それも自ずと作り直すことになります。


###UITableView のセルの高さを Size Class ごとに指定する

UITableViewのセルの高さも、それ自体を IB から Size Class ごとに指定することはできないので、IB上で Size Class ごとにオブジェクトをつくる・・・のは面倒なので、 <span class="deco" style="font-weight:bold;">UITraitCollection で Size Class を判定</span>し、それに合わせたセルの高さを heightForRow のデリゲートメソッドで返すことにしました。

```
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    // iPadかどうかの判定
    BOOL isPad = NO;
    if ([self respondsToSelector:@selector(traitCollection)]) {
        
        isPad = self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular;
    }
    else {
        isPad = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad;
    }
    
    return isPad ? 160.0 : 80.0;
}
```


なお、上記コードは、画面の向きが Portrait であることを前提としています。また、iOS 7 には `traitCollection` プロパティがないので、従来の方法（UI_USER_INTERFACE_IDIOM）で iPad スクリーンかどうかを判定しています。


###xib と Size Class

xib も storyboard と同様に、File Inspector で [Use Size Classes] にチェックを入れることで Size Class が使用できます。（が、プレビューが結構デタラメな感じになります）



##Asset Catalog と Size Class

Asset Catalog でも Size Class ごとに画像を登録できます。


![](http://f.st-hatena.com/images/fotolife/s/shu223/20141006/20141006073205.jpg)


IB だけで Size Class で複数画面対応しようとすると上述したように「オブジェクト自体をSize Classごとにつくる」ということをしないといけなくなる場合があって、それやると Constraint がカオスになるので、これはこれで便利です。例えば UIButton に指定する画像を使い分けたいときとか。


ただ僕が試してみたところ、何かやり方が悪かったのか、<b>うまく適用されなかった</b>ので、さっさとあきらめて、その上のプルダウンにある、<span class="deco" style="font-weight:bold;">デバイス（Idiom）ごとに Asset を登録する</span>方法で iPhone / iPad の画像使い分けを行ないました。


#Asset Catalog のベクター形式サポート

ここから Asset Catalog のベクター形式の話。いろいろとわかりやすい記事が出ています。

- [Asset Catalog でベクターが扱えるようになりました！](http://dev.classmethod.jp/references/xcode-6-asset-catalog-vector/)
- [USING VECTOR IMAGES IN XCODE 6](http://martiancraft.com/blog/2014/09/vector-images-xcode6/)（英語）
- [Using PDF images in iOS apps](http://mattgemmell.com/using-pdf-images-in-ios-apps/)（英語）

あと、WWDC の "What's new in Interface Builder" セッション動画も参考になります。


要点だけ書くと、

- Xcode 6 の Asset Catalog ではベクター形式の画像をサポート
- <b>2x, 3xとか複数リソースを登録する必要がなくなる</b>（1ファイルだけでOK）
- <b>SVG ではなく PDF 形式</b>


##ベクター形式サポートに関するよくある誤解

よくあるというか、僕が誤解してただけなんですが、このベクター形式サポートは、


アプリ内で <b>どんな大きさで使っても奇麗に描画されるものではない</b> ということです。


どういうことかというと、プロジェクトに入れるのはベクタ形式でも、<b>ビルドする際にラスタライズされる</b> （WWDC のセッション "What's new in Interface Builder" 参照）ので、<b>アプリ実行時に使用されるのは結局ビットマップ形式 </b>ということです。


なので、普通に拡大したらジャギります。ベクター的に使いたいときには SVG とかを（Asset Catalog じゃないところで）プロジェクトに追加して、今まで通り UIBezierPath とか CGPath で描画しましょう。（このベクタ描画については、[拙著](http://d.hatena.ne.jp/shu223/20130528/1369714635)にも書いてあります）


##ベクター画像のサイズ

大きめに書き出すといい、という情報もありますが、<b>1x サイズで書き出す</b> （つまり所望のポイントサイズ）というのが正しいです。ビルド時にラスタライズ、つまり 2x, 3x サイズのビットマップ形式の画像を生成してくれます。


大きめに書き出すといい、というのは上述の、拡大して使用するとジャギる、ということに端を発する誤解かと思いますが、それは本来あるべきサイズより拡大して用いるからであって、そういう場合はその拡大した状態でのサイズを本来あるべきサイズ（1x サイズ）として指定すればいいのであって、大きめサイズを指定するということではないかと。（わかりにくい文章ですみません）


あと困ったのが、<b>Illustrator で書き出したサイズと、Asset Catalog に入れたときのサイズが合わない</b>という問題。こちらの解決方法は今のところ不明・・・


#その他複数画面対応に関連するメモ

##謎の「-16」

Xcode 6 で AutoLayout を設定しまくってると必ずぶちあたる、「<b>両端にぴったり 0 でAutoLayout設定しようとすると現れる、謎の "-16"</b>」問題。下記記事で、その正体、対処法が解説されています。

- [SizeClassesとXcode6でのAutoLayoutの謎マージン](http://qiita.com/uskiita/items/c643f5868f60b496911e)


##6/6 Plus に最適化される条件

ご存知の通り、既存アプリに何も修正をいれない状態で 6 / 6 Plus で実行すると単純に拡大されるだけです。では、何を条件としてそれが 4.7inch, 5.5inch 最適化モード（※単純拡大されなくなった状態）に切り替わるのか、のメモです。


- Launch 画像を登録する

Asset Catalog で、4.7inch, 5.5inch 用の Launch 画像を入れると、それぞれに最適化されたものとして描画されるようになります。


- Launch Screen Fileを指定する

起動画面を Storyboard でつくれるようになった、っていうアレです。そういう Storyboard をプロジェクトに含めるだけならセーフ、[General] > [App Icons and Launch Images] > [Launch Screen File] でその Storyboard を指定すると 4.7, 5.5 対応が必要になります。


- Asset Catalog への 3x 画像の登録

は、<b>トリガにはなりません</b>でした。


6 Plus で表示してみると、普通にその 3x 画像が適用されましたが、単純拡大のまま。