いものやま。

雑多な知識の寄せ集め

いろんな画面サイズに対応する方法について。

相変わらず、SpriteKitのサンプルコードを読んでる。
その中で、いろんな画面サイズに対応させるためのコードがあったので、ちょっとまとめてみた。

ポイントとピクセル

まず、基本的なこととして、画面サイズには論理的なサイズ(単位はポイント)と物理的なサイズ(単位はピクセル)がある。

プログラムを作っているときには、基本的には論理的なサイズだけを気にしていればいいけど、あらかじめ用意した画像を使う場合には、物理的なサイズも気にする必要がある。

画像ファイルの扱い

画像ファイルを用意するとき、ファイル名に@2x@3xをつけると、それらはそれぞれスケールが2倍、3倍の画像として扱われる。

なお、論理的なサイズと物理的なサイズ、および、スケールの関係は、

(論理的なサイズ) x (スケール) = (物理的なサイズ)

という感じ。

当然、画像ファイルのピクセル数は(ファイルが同じなら)固定なので、論理的なサイズとスケールの間で調整がされることになる。
(それを使うTipsについては、後述)

デバイスごとの画面サイズと、優先される画像ファイルのスケール

デバイスごとの論理的な画面サイズと、優先して使われる画像ファイルのスケールは、以下のとおり。
(※横向き)

デバイス 画面サイズ 優先されるスケール
iPhone 4 / 4S 480 x 320 @2x
iPhone 5 / 5s / 5c 568 x 320 @2x
iPhone 6 667 x 375 @2x
iPhone 6 Plus 736 x 414 @3x
iPad / 2 / mini 1024 x 768 (@1x)
iPad 3 / 4 / Air / Air 2 / mini 2 / mini 3 1024 x 768 @2x

なお、優先されるスケールの画像ファイルが用意されていない場合も、他のスケールの画像ファイルがあれば、その画像ファイルが使われる。
ただし、拡大/縮小が行われるので、画像はやや荒くなる感じ。

オートレイアウトとサイズクラス

論理的なサイズを導入することで、物理的なピクセル数の差をあまり気にしなくて済むようになってはいるのだけど、その論理的なサイズにも差があるというのが実情。
なので、これらの差を調整するために、オートレイアウトという機能が用意されている。

このオートレイアウトが何かというと、ビューに制約を持たせることで、その制約にしたがってビューのサイズを自動で調整させるようにするというのもの。

例えば、SpriteKitのサンプルコードを見てみると、ルートにあるビューに対して、

  • 親と上辺を揃える
  • 親と下辺を揃える
  • 親と左辺を揃える
  • 親と右辺を揃える

という制約を入れることで、画面を全部覆うようなサイズに変更されるようになっている。
(実際にデバッガでサイズを確認してみると、例えばiPhone 4Sなら480 x 320というサイズが返ってくるし、iPhone 5sなら568 x 320というサイズが返ってくる)

ただ、これでもうまくレイアウトが調整できない場合がある。
そこで用意されているのが、サイズクラスという仕組み。

サイズクラスでは、画面のサイズがいくつかのクラスに分類されていて、そのクラスごとに表示するビューを選択したり、オートレイアウトの制約を入れたりすることが出来るようになっている。

このあたりについては、以下の書籍が参考になった。

サンプルコードのテクニック

以下では、サンプルコードで使われていたテクニックを紹介する。
なお、iPhoneでもiPadでも、同じだけの情報をユーザに与えたい」という要求に応えるためのテクニックになってる。

同じ画像をiPhoneiPadで使う

iPhoneiPadでは、論理的な画面サイズが異なる。
なので、同じ一枚絵を表示させようとしても、画面サイズに対する割合というものが、iPhoneiPadでは異なってくる。

具体的な例を出すと、例えば何か画像を用意して、その画像を使って画面全体を覆いたいとする。
なお、画像がちょっとはみ出すのは問題ないが、はみ出し過ぎて、画像の内容があまり見えなくなてしまうのは困るとする。

このとき、例えば"cover@2x.png"という1472ピクセル x 828ピクセルの画像を用意したとする。
この画像からUIImageやSKSpriteNodeを作ると、その論理的なサイズは736ポイント x 414ポイントになる。
これは、iPhoneなら画面全体を覆うサイズになる(iPhone 6 Plus以外だと、ちょっとはみ出る)けど、iPadだと画面全体を覆うには小さすぎる。

一方、"cover@2x.png"の画像サイズを2048ピクセル x 1536ピクセルにしたとする。
そうすると、UIImageやSKSpriteNodeの論理的なサイズは1024ポイント x 768ポイントになり、iPadの画面全体を覆えるようになる。
しかし、今度はiPhoneで表示させようとしたときに、大部分がはみ出してしまって困ってしまう。

そこで、どうするのかというと、サンプルコードでは次のようなテクニックを使っていた。

まず、"cover@2x.png"という2944ピクセル x 1656pxの画像を用意。
そして、ViewController#viewDidLoad()で、次のようにしている。

class ViewerController: UIViewController {
  ...
  override func viewDidLoad() {
    var image = UIImage(named: "cover")!
    if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
      image = UIImage(CGImage: image.CGImage, scale: image.scale * 2.0, orientation: UIImageOrientation.Up)!
    }
    coverView.image = image
  }
  ...
}

何をやっているのかというと、デバイスがiPhoneの場合には、画像のスケールを2倍にしている。
そうすると、先ほどの

(論理的なサイズ) x (スケール) = (物理的なサイズ)

という式から、論理的なサイズが1/2になる。
(物理的なサイズは変わらないことに注意!)

これによって、

デバイス変換前変換後
論理サイズスケール論理サイズスケール
iPhone1472 x 8282.0736 x 4144.0
iPad1472 x 8282.01472 x 8282.0

となるので、iPhoneの画面全体を覆えるし(iPhone 6 Plus以外だとちょっとはみ出る)、iPadの画面全体も覆える(ちょっとはみ出る)ようになる。

SKSceneをiPhoneiPadで使う

SpriteKitでは、UIViewを継承したクラスであるSKViewをビューとして配置して、そこにSKSceneをセットすることで、SKSceneの内容が描画されるようになっている。
ここで、やはり先程と同様の問題があって、iPhoneiPadだと論理的なサイズが2倍近く違うので、表示される内容に大きな差が出てしまう。

これを解決する一つの方法として、SKViewのサイズからSKScene内の各サイズを計算して出し、それを使うというのが考えられる。

ただ、サンプルコードでは次のような方法を使って、この問題を解決していた。

class ViewController: UIViewController {
  ...
  override func viewWillAppear(animated: Bool) {
    ...
    AdventureScene.loadSceneAssetsWithCompletionHandler { loadedScene in
      var viewSize = self.view.bounds.size
      if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
        viewSize.height *= 2
        viewSize.width *= 2
      }
      self.scene = loadedScene
      self.scene.size = viewSize
      self.scene.scaleMode = .AspectFill
      ...
    }
  }
  ...
}

何をやっているのかというと、iPhoneの場合にはSKSceneのサイズを画面のサイズの2倍にして、そして、スケールモードをSKSceneScaleMode.AspectFill(縦横の比を変えないまま拡大/縮小してシーン全体が表示されるようにするモード)にしている。

こうすることで、

デバイス画面サイズSKSceneの
サイズ
SKSceneが
表示されるときの
拡大率
iPhone 4 / 4S480 x 320960 x 64050%
iPhone 5 / 5s / 5c568 x 3201136 x 64050%
iPhone 6667 x 3751334 x 75050%
iPhone 6 Plus736 x 4141472 x 82850%
iPad1024 x 7681024 x 768100%

となるので、SKSceneのサイズはほぼ同じになり、iPhoneではそれが50%のサイズに縮小されて表示されるようになる。

今日はここまで!