iOS 開発で storyboard と xib をうまく使い分けるプラクティス

Web エンジニアだったはずがひょんなことから iOS アプリを書き始めてはや3ヶ月。ヘルスケア事業部の濱田です。

iOS アプリで画面遷移を実現するためには様々な方法があります。

  • コードのみを使う方法
  • xib を使う方法
  • storyboardを使う方法
  • etc.

初めはかなり混乱しましたが、最終的には storyboard と xib の合わせ技に落ち着きました。 今回はこの方法についてご紹介します。

storyboard を使うか、xib を使うか、それが問題だ

アプリの UI 部品の配置は結構たいへんな作業です。とくに Autolayout の制約の設定などは、コードのみで設定するのは困難でしょう。Interface Builder の支援をなるべく活用したいところです。 そこで、storyboard もしくは xib ファイルを利用して ViewController(以下 VC) を 書いていくわけですが、どちらにも利点と欠点があり、場合によって使い分ける必要があります。

よくある定義の仕方としては、以下の2つがあるようです。

storyboard に複数の VC の定義と遷移を一緒に書く

Pros

  • アプリの遷移を集約して書くことができ、処理の流れが理解しやすい

Cons

  • storyboard がどんどん大きくなって編集がコンフリクトする
  • 同じ VC を複数個置きたいと思っても、ビューの定義を再利用できない

xib ファイルごとに VC を定義して、遷移の定義はコードで行う

Pros

  • 画面を再利用できる

Cons

  • コードでつないでいると、VCのつながりが把握しにくい

前者の方法だとコードは少なくなりますが、VC の再利用を行うことはできません。 後者の方法を使うと、VC を複数の箇所で使いまわせるのですが、画面の流れを把握するのは難しくなります。

そこでちょっとだけ工夫をして、画面の流れは storyboard で定義しつつ、VC のビュー定義は xib ファイルにおいておく折衷方式にしました。

xib と storyboard を組み合わせて使う

折衷方式には、以下のようなメリット/デメリットがあります。

Pros
  • VC のクラス定義、ビュー定義がそれぞれ1ファイルに対応するので取り回しやすい
  • VCを複数のstoryboardで再利用できる(DRY!)
Cons
  • storyboard の画面上では UI パーツの配置が確認できない

今回取り組んだアプリでは同じ VC が異なる storyboard の遷移の中に現れることがしばしばあり、上記の折衷方式で効率よく実装できました。

作業手順

例として、FirstViewController, SecondViewController という2つの画面を持つアプリを考えます。

FirstViewController あるボタンを押すと、SecondViewController がモーダル表示されるという簡単なアプリです。

f:id:manemone:20150623204145g:plain

xib ファイルにビューのみを定義する

まず、FirstViewController、SecondViewController それぞれのビュー定義を行う xib ファイルを作成します。 xib の中には、VC で表示するための UIView を一つおき、その中に画面部品を配置していきます。(VC ではなく、View のみを定義するのがミソ)。

File's owner で xib と VC を関連付ける

次に、xib の File's owner に SecondViewController クラスを設定します。これによって、xib と SecondViewController の紐付けが行われ、ビュー要素へのアウトレットを持つことができるようになります。

f:id:manemone:20150623203041p:plain

f:id:manemone:20150623203043p:plain

VC の loadView() をオーバライド

さらに、VC の loadView() を以下のようにオーバーライドします。

// Yes, we're on Swift :-)
class SecondViewController: UIViewController {
  ...
    override func loadView() {
      if let view = UINib(nibName: "SecondView", bundle: nil).instantiateWithOwner(self, options: nil).first as? UIView {
        self.view = view
      }
    }
  ...
}

nibname: にはさきほどの xib ファイルの名前を書きます。 loadView() はその名の通り、VC が管理するビューを読み込むためのメソッドです。通常の動作では VC が格納された storyboard や xib ファイルからビューを読み込むようになっています。ここでは、必ず先ほどの xib のビューを読み込むように設定しておきます。

storyboard でフローを定義する

ストーリーボードでビューの流れを定義してみましょう。FirstViewController のボタンを押すと SecondViewController がモーダルとして表示されるようにします。

VC を一つ追加して、中の View を削除します。 Indentity Inspector の Custom Class で、先ほど作ったクラスを指定し、scene の中に指定したクラス名が表示されれば OK です。この状態でシミュレータで走らせてみると、xib ファイルに記述したビューが読み込まれていることがわかるはずです。

f:id:manemone:20150623203045p:plain

f:id:manemone:20150623203047p:plain

segue で接続

あとは、通常の storyboard 作成と同じように、追加した VC をマニュアル segue で接続し、適切な identifier を付けておきます。ここではそれぞれ "buttonA"、"buttonB" としました。 VC の方では、遷移を促すアクションが発生したとき、設定した identifier で segue を実行すれば OK です。

f:id:manemone:20150623203044p:plain

プロパティを IB 経由で与えて調整する

画面の背景色等、UIに与える初期値を storyboard 側から調整したいときは、Interface Builder の User Defined Runtime Attributes を活用するとよいです。例えば、2つめの SecondViewController に表示されるラベルの色を変更したいときは以下のようにします。

SecondViewController で以下のように設定しておきます

class SecondViewController: UIViewController {
  ...
    @IBOutlet weak var label: UILabel!

    // この変数に IB から色をセット
    var labelBackgroundColor = UIColor(red: 240.0/255, green: 118.0/255, blue: 101.0/255, alpha: 1.0)

    override func viewDidLoad() {
      super.viewDidLoad()

        // 変数 labelBackgroundColor に設定された色をラベルの背景色にする
        self.label.backgroundColor = labelBackgroundColor
    }
  ...
}

Interface Builder の storyboard で SecondViewController を選択し、User Defined Runtime Attributes に色を設定します。

f:id:manemone:20150623204227p:plain

以上で、冒頭の GIF と同じ挙動の遷移が実現できました。

載せきれなかったコードは以下のリポジトリに置いてあります: ReusableVCDemo

いかがでしょうか。関わった仕事では、基本的に上述のやりかたで VC のビューと画面遷移を記述し、storyboard をまたぐ遷移だけコードで記述するようにしています。 画面遷移を細かい storyboard に分割して定義する場合、同じ VC が複数の storyboard に現れるのはよくあることです。そんなときでも VC を DRY に記述しつつ、かつ storyboard の良さであるビジュアルベースの遷移定義ができて便利ですので、よろしければ参考にしてみてください。