この記事は【その2】ドリコム Advent Calendar 2015 - Adventarの15日目です。
14日目はテラさん「絵について思ったこと」でした。
ちなみに【その1】もありますので、ぜひこちらもご覧ください!
自己紹介
2015年2月に企画職としてドリコムに中途入社し、現在は研究開発部にて新規サービスの立ち上げをしています。
この会社に入ってコーヒーを飲む機会と筋トレをする機会が増えました。
最近の休日はスカッシュをやることが多いです!(仲間募集中!!!)
来年はSASUKEに出たいです。
この記事を書くに至った背景
最近会社で、iOSアプリの開発をしています。ドリコムに入るまでは、**iOSアプリのviewはすべてコードで書いていました。**ですが、僕の目の前に座っている先輩に「え?viewを全部コードで書いているって?storyboard使ったほうが便利な場合が多いよ (圧)」というありがたいお言葉を頂いたので、新しいプロジェクトではview周りをstoryboardをベースにして作ることにしました(以前からやらなくちゃと思っていたのも後押しに..)。
いざstoryboardでviewを作り始めてはみたものの、
「むぅ...どうやってstoryboardとxibを使い分けるのが正しいんだ....」
この疑問が真っ先に浮かびました。その状態から実際に使い続けて、最近やっと自分なりの使い分けが定着してきたので、簡単にまとめていきたいと思い、このネタで書くに至りました。
これからstoryboardやxibを使ってみよう!という人向けの記事です。
storyboardとxibはどう使い分けるか
「ユーザーの一連のフロー」や「同一の意味をもった画面群」毎にstoryboardを作る
はじめにどのような単位でstoryboardを作っていくか、ということについて書いていきます。
前提として、基本的にviewはすべてstoryboard経由で呼び出すようにしています。なので単一のxibを作り、それをベースのviewとして呼び出すということは極力しないようにしています(xibはあくまでviewの中の1パーツとして呼び出すのを基本とする)。
肝心のどういう単位で作っていくかということなのですが、見出しの通り、2パターンあります。
1つ目は当たり前のことですが、ユーザーのフロー単位で作成するパターンです。
例えばユーザー登録や、カメラで撮影 => 情報を入力 => 投稿のような単位で作成していきます。
2つ目は、近い意味の画面をまとめるパターンです。
例を挙げるとすると、全画面で共通で呼び出すmodal viewなどが該当します。どのフローにも属さないような画面をxibで作っていくとファイル数が増えてしまいます。なので、そういった意味合いの近い画面をまとめるためにstoryboardを一つ作り、その中にそれぞれのviewを作っていくようにしています。
xibはメインとなるviewのパーツとして使用する
続いてxibについてすが、前項でも軽く触れましたが、あくまでパーツとして使用するようにしています。
パーツとして作成するので、ファイル数が多くなりがちです。そのため必ずファイル名は該当のview controllerの先頭をPrefixにつけるようにしています。
それぞれのviewをどの粒度で分けるかについては、後ほど実例を交えて説明します。
その他View周りで意識していること
- storyboardやxibのファイルが増えすぎないようにする
- 色はstoryboardやxib上で付けず、UIColorを継承したクラスを作り、コードで色付けする。
- ボタンに紐づくアクションはどこに持たせるか?
- 遷移系のアクション => それぞれのviewに持たせる
- それ以外 => viewを操作しているcontrollerに持たせる(他の画面でも使うようなアクションは処理を委託するためのクラスを作ってそこに投げる)
実例
言葉だけでは伝わりにくいかと思うので、簡単な例を元に説明していきたいと思います。
以下のような、写真を投稿する画面があったとします。今回はこちらを実際に作っていきます。
どう分割するか
画面の分割は機能の持つ役割毎に行います。
この画面の場合以下に記載する3つの役割があります。
- 写真を設定する(青)
- 写真に関する情報を入れる(赤)
- 投稿させる(黄)
なので今回は、以下のように分割します。
実際に作っていく
※以下autolayoutについては解説を省略します。実際にはそれぞれ制約を付けてあるので、詳しくはこちらをご覧ください。
親viewの作成
まず、storyboardに以下のような親viewを配置し、それぞれを以下のように名前を付けます。
- PhotoPostSelectedParentView
- PhotoPostInfoParentView
custom viewの作成
続いてそれぞれに乗せるcustom viewを作っていきます。
写真選択用custom viewの作成
写真を選択させるためのcustom viewを作成します。
- PhotoPostSelectedView.swift
- PhotoPostSelectedView.xib
情報入力用custom viewの作成
次に写真に関する情報を入力するためのcustom viewを作成します。
- PhotoPostInfoView.swift
- PhotoPostInfoView.xib
ここまででcustom viewの作成は完了です。
親viewにaddsubviewしていく
最後に、それぞれを各親Viewにaddsubviewをしていきます。
autolayoutを使用しているので、custom viewをaddsubviewする際には、コードでautolayoutをつけるのをお忘れずに。
以下はそのコードとなります。
import UIKit
class PhotoPostViewController: UIViewController {
@IBOutlet weak var photoPostSelectedParentView: UIView!
@IBOutlet weak var photoPostInfoParentView: UIView!
var photoPostSelectedView: PhotoPostSelectedView?
var photoPostInfoView: PhotoPostInfoView?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
photoPostSelectedView = UINib(nibName: "PhotoPostSelectedView", bundle: nil).instantiateWithOwner(self, options: nil).first as? PhotoPostSelectedView
addSubviewWithAutoLayout(photoPostSelectedView!, parentView: photoPostSelectedParentView)
photoPostInfoView = UINib(nibName: "PhotoPostInfoView", bundle: nil).instantiateWithOwner(self, options: nil).first as? PhotoPostInfoView
addSubviewWithAutoLayout(photoPostInfoView!, parentView: photoPostInfoParentView)
}
// 親viewに対してフルで表示するためのautolayoutの制約
private func addSubviewWithAutoLayout(childView: UIView, parentView: UIView) {
parentView.addSubview(childView)
parentView.addConstraint(NSLayoutConstraint(item: childView, attribute: .Top, relatedBy: .Equal, toItem: parentView, attribute: .Top, multiplier: 1.0, constant: 0))
parentView.addConstraint(NSLayoutConstraint(item: childView, attribute: .Right, relatedBy: .Equal, toItem: parentView, attribute: .Right, multiplier: 1.0, constant: 0))
parentView.addConstraint(NSLayoutConstraint(item: childView, attribute: .Bottom, relatedBy: .Equal, toItem: parentView, attribute: .Bottom, multiplier: 1.0, constant: 0))
parentView.addConstraint(NSLayoutConstraint(item: childView, attribute: .Left, relatedBy: .Equal, toItem: parentView, attribute: .Left, multiplier: 1.0, constant: 0))
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
これで以下のように表示できたかと思います。
簡素な内容となってしまいましたが、基本的にはこの考えを応用しviewを作っていくことになります。
コードを見たい方はこちらに上げてありますのでご自由にどうぞ!
最後に
コードだけでviewを書いた場合との比較
いくつかコードだけで書いていた時の比較をいくつか挙げていきます。
良い点
- 当たり前ですが、view周りのコードが削減でき見通しが良くなる(なんだかんだこれが一番)。
- デザインだけ当てたものであれば、コードだけで書いていた時よりも早く作ることができる。
- 結果として早い段階から細かいデザイン面の修正が可能
- 認識のズレを早期に対処することができる
- 他のメンバーに作りを共有するコストが減る
- viewが可視化できていることで、不用意に余計なviewを作りにくくなる(意識の問題でもありますが...)
悪い点
- viewの継承がしにくい
- 共通のパーツをベースに拡張していくには、微妙な差分くらいでも再度xibを作ってやらなくてはいけない。コードのときは結構使っていただけに個人的には一番うーんというポイント。
- ルールを決めないで作っていくと理解に苦しむviewがいくつかでてきてしまう
使いにくいなぁと思う点はいくつかありますが、それでも今後も何か作る際には採用してやっていきたいと思っています。
今回挙げた使い分けは、あくまで僕が使っていての結論であって、他にももっと良い方法がたくさんあるかと思います。
何より大事なのは、このプロダクトは、こういうルールでやるんだと決め、それを早いうちから定着させていくことだと、改めて思いました。
以上で【その2】ドリコム Advent Calendar 2015 - Adventarの15日目の記事はおしまいです。
16日目はchu-hiさんです!
【その1】ドリコム Advent Calendar 2015 - Adventarの方も良かったら見てみてください!
今年も残り僅か!みなさん後悔のないようにお過ごし下さいませ!