その後のその後

iOSエンジニア 堤 修一のブログ github.com/shu223

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

とあるお仕事で、iPad をサポート(= Universal 化)してほしいという要望があり、せっかくなので iPhone 6 / 6 Plus (4.7 / 5.5 inch スクリーン)もサポートしようってことで、新しい Xcode 6 の新しい仕組みである Size Class を使って複数画面対応を行ないました。あと、同じく Xcode 6 から Asset Catalog でベクター形式がサポートされるようになったので、こちらもトライしてみました。


で、そのときに学んだ諸々の断片的なメモです。勘違いもあるかもしれないのでその際は優しくご指摘いただけますと幸いです。


※ちなみに Size Class やベクター形式画像の使用は iOS 8 以上縛りではありません。単に Xcode 6 の新機能というだけなので。

Size Class の前提知識をつける

「Size Classsとは」ってところを学ぶには、クラスメソッドさんの下記記事が日本語、図解入りでとてもわかりやすいです。


Size Class 機能を使って複数画面サイズ対応する手順のチュートリアル動画。英語ですが、音声なしでもわかりやすいです。Xcodeのプレビュー機能を駆使するのめちゃめちゃ大事。

シーン別 Size Class の指定方法

AutoLayout を Size Class ごとに指定する

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


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

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


で、こちらは IB の Attributes Inspector で設定します(わかりにくい!)



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

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

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


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


(2014.10.30追記)各コンポーネントのフォントにも Size Class ごとの指定ができます


この小っちゃい + をクリックすると、



こんな感じで Size Class ごとに設定できます。


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

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


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

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

UITableViewのセルの高さも、それ自体を IB から Size Class ごとに指定することはできないので、IB上で Size Class ごとにオブジェクトをつくる・・・のは面倒なので、 UITraitCollection で Size Class を判定し、それに合わせたセルの高さを 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 は AutoLayout とかは普通に指定できますが、Size Class ごとの指定ができません(よね??Xcodeは操作方法知らないだけで実はできるとかがよくある。。)


自分は、スクロールビューに動的に貼るビューとか、再利用したいビューとかは xib でインターフェースをつくってそこから生成するということを結構よくやるのですが、Size Class の指定ができないってことで、それらを Storyboard に移し替えました。で、その関係で、あんまり使ってなかった `childViewController` とかも使うようになりました。


(2014.10.2追記)コメント欄より xib でも Size Class 使えますよという情報をいただいて改めて確認してみたところ、File Inspector で [Use Size Classes] にチェックを入れ忘れてただけで、普通に使えました。

(2014.10.6追記)Asset Catalog と Size Class

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



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


ただ僕が試してみたところ、何かやり方が悪かったのか、うまく適用されなかったので、さっさとあきらめて、その上のプルダウンにある、デバイス(Idiom)ごとに Asset を登録する方法で iPhone / iPad の画像使い分けを行ないました。

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

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


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


要点だけ書くと、

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

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

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


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


どういうことかというと、プロジェクトに入れるのはベクタ形式でも、ビルドする際にラスタライズされる *1ので、アプリ実行時に使用されるのは結局ビットマップ形式 ということです。


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

ベクター画像のサイズ

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


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


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

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

謎の「-16」

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

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 画像の登録

は、トリガにはなりませんでした。


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




*1:WWDC のセッション "What's new in Interface Builder" 参照