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 がモーダル表示されるという簡単なアプリです。
xib ファイルにビューのみを定義する
まず、FirstViewController、SecondViewController それぞれのビュー定義を行う xib ファイルを作成します。 xib の中には、VC で表示するための UIView を一つおき、その中に画面部品を配置していきます。(VC ではなく、View のみを定義するのがミソ)。
File's owner で xib と VC を関連付ける
次に、xib の File's owner に SecondViewController クラスを設定します。これによって、xib と SecondViewController の紐付けが行われ、ビュー要素へのアウトレットを持つことができるようになります。
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 ファイルに記述したビューが読み込まれていることがわかるはずです。
segue で接続
あとは、通常の storyboard 作成と同じように、追加した VC をマニュアル segue で接続し、適切な identifier を付けておきます。ここではそれぞれ "buttonA"、"buttonB" としました。 VC の方では、遷移を促すアクションが発生したとき、設定した identifier で segue を実行すれば OK です。
プロパティを 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 に色を設定します。
以上で、冒頭の GIF と同じ挙動の遷移が実現できました。
載せきれなかったコードは以下のリポジトリに置いてあります: ReusableVCDemo
いかがでしょうか。関わった仕事では、基本的に上述のやりかたで VC のビューと画面遷移を記述し、storyboard をまたぐ遷移だけコードで記述するようにしています。 画面遷移を細かい storyboard に分割して定義する場合、同じ VC が複数の storyboard に現れるのはよくあることです。そんなときでも VC を DRY に記述しつつ、かつ storyboard の良さであるビジュアルベースの遷移定義ができて便利ですので、よろしければ参考にしてみてください。