machio Development Diary

思いつきで技術的なことをつらつらと

多人数でのiOS開発におけるStoryboardとの向き合い方(1画面1Storyboard)

※僕が試行錯誤したストーリーが前半を占めてるので興味がなければ読み飛ばしてください

Storyboardと付き合う

StoryboardやAutoLayoutの出現によって、iOSアプリケーション開発に置けるViewの実装のハードルは確実に低くなりました。弊社のプロダクトでも惜しみなくStoryboardを使用しています。

しかし多くの方がStoryboardとの向き合い方について頭を悩まされてきたと思います。

例として以下のCookPadさんの記事では

1つのStoryboardにViewControllerを複数配置して、Segueで画面遷移を実装(しかしViewは再利用したいのでxibファイルの形式で切り分ける)

という手法が取られています。

techlife.cookpad.com

一瞬でこれだ!と思い、採用させていただきました。似たようなUIを何度も再利用していくアプリとの親和性は相当高い思います。

この設計で一生開発していくんだと思っていました。最初は、、、

Storyboardの闇に触れる

正直1人で開発を進めていくのであれば、1Storyboardに全て詰め込んでしまうのが一番シンプルで楽かなと思います。

しかし複数人開発になった瞬間にそういうわけにはいきません。 恐るべきことに異なるbranchで同じStoryboardを触るとほぼ確実にconflictが起きます

しかもこのconflictが厄介で、conflictが発生した瞬間そのファイルはxcodeで開くことができなくなり、コードを見て愚直に1つ1つ問題を解消するしかなくなります。それも実力不足の僕の場合高確率で失敗するので、泣く泣く片方の変更を捨てることもしばしばです。

PRをだすたびに赤く染まるGithubの画面。「いっそのことStoryboardなんて使わなければよかった」、そんな考えが頭をよぎりました。

1画面1Storyboardという選択

そんなある日、某グルメサービスのiOSエンジニアさんとお話する機会があり、この悩みをぶつけたところ、「弊社は1画面につき1Storyboardでやってますね」という答えが帰って来ました。

確かに画面ごとにファイルを分けて画面ごとにタスクを分担すれば、まずconflictは起こりません。最初は「何を言ってるんだ、Segueを殺して何が楽しい」と憤ってた僕ですが、これが最高でした。

以下で詳しく説明します。

具体的な方法

別のStoryboardのViewControllerに移動するのは実は大して難しくありません。

let storyboard = UIStoryboard(name: "Hoge", bundle: nil) // storyboardのインスタンスを名前指定で取得
let nextVC = storyboard.instantiateInitialViewController() as! UIViewController // storyboard内で"is initial"に指定されているViewControllerを取得
self.present(ViewControllerVC, animated: true, completion: nil) // presentする

みたいに3行でいけます。基本的には

  • 1ViewControllerにつき1Storyboardを用意してあげる
  • 各Storyboardに適切な名前をつける
  • 上記のコードで画面遷移!

これで大丈夫でしょう。ですがStoryboard経由でViewControllerを取得してる感が鬱陶しいので、弊社ではもう少し精錬して使用しています。

以下の3STEPです。

1. 以下のようなprotocolを定義します。

protocol StoryboardInstantiable {
    static var storyboardName: String { get }
    static var bundle: Bundle? { get }
}

extension StoryboardInstantiable where Self: UIViewController {
    static var bundle: Bundle? {
        return nil
    }
    static func instantiate() -> Self {
        let storyboard = UIStoryboard(name: storyboardName, bundle: bundle)
        return storyboard.instantiateInitialViewController() as! Self
    }
}

2. これを各ViewControllerに継承させる

storyboardNameをそれぞれで定義しましょう。

class HogeViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    // 諸々の処理
}

extension HogeViewContoller: StoryboardInstantiable {
    static var storyboardName: String {
        return "Hoge"
    }
}

3. 遷移のコードは2行に

let nextVC = HogeViewController.instantiate() // これだけでStoryboardに紐づいたHogeViewControllerを取得
self.present(ViewControllerVC, animated: true, completion: nil) // presentする

かなりシュッとしたと思います!ViewControllerベースで次の画面取得してる感が好きです。

終わりに

このやり方はあくまで一例であって、他にも色々な設計があり、それぞれが長所・短所を持っていると思います。その開発の環境に応じた最適なものを採用するのが一番ですが、多人数開発をするとき、この手法をチラッと思い出していただけると本望です。