🦅

[iOSアプリ開発] プロジェクト新規作成をしたらはじめにやってること(おまけ1) 開発の時だけ表示するViewControllerを作る

2021/02/05に公開

プロジェクト新規作成をしたらはじめにやってること

こんにちは。
iOSアプリ開発のちょっとしたTipsなどを書いていければと思います。

今回も、iOSアプリ(主にiPhone用)の開発を新しく始めたときに、いつもやってるなぁと思うことを短くまとめようと思います。

で、表題は「おまけ」と書きました。
というのも、今記事で書くことは「こうすべき」というような話ではないからです。
自分が個人アプリを作るときにいつも使っている方法のご紹介なので
「あ、それ便利かも」と思っていただける方がいれば参考になさってください。

何を用意しようとしているのか

アプリを開発していると、画面をいくつも作ることになると思います。
次第にA画面からB画面、C画面に遷移してD画面へ・・・みたいに奥に奥に画面が入っていくと思います。そうすると、D画面の実装をしていざ動作確認をしたいときに前述の操作をしてその画面までたどり着かなくてはならなくなります。

ここの無駄を取り払うために「タップ1回でD画面に進む」という開発用の画面を用意しておきたいわけです。

この記事ではその画面用のViewControllerをBootstrapViewControllerと名づけました。
Bootstrapというと今ではCSSフレームワークの名前で有名ですが、「起動の」という意味もあるとのことでこういう名前にしました。

https://www.weblio.jp/content/bootstrap

これはただの個人的に勝手につけた名前なので、
たとえば「EntranceViewController」とか「DevelopViewController」とか、あるいは別の分かりやすい名前にしていただければと思います。

今記事ではBootstrapViewControllerという名前でいかせていただます。

BootstrapViewController

まずBootstrapViewControllerという名前のViewControllerクラスを作ります。
で、それと並びでBootstrap.storyboardも作ります。

BootstrapViewController.swift

まずは空っぽのBootstrapViewControllerを定義

class BootstrapViewController: UIViewController {

}

Bootstrap.storyboard

で、次にストーリーボードの中身はこんな感じにします。

BootstrapViewControllerとして ViewController を置き、
画面全体をテーブルビューにして、ストーリーボード上でdelegatedatasourceBootstrapViewControllerにセットしてやります。
テーブルビューにはBasicスタイルのセルをidentifierrowに設定して1個置いておきます。

ストーリーボードの設定はこれだけ。

下ごしらえ

BootstrapViewControllerの中身を作っていきます。

セクションと行を定義

タプルの仕組みと typealias を使ってSectionRowという簡単な定義をしておきます。

class BootstrapViewController: UIViewController {
    
+   private typealias Section = (String, [Row])
+   private typealias Row = (String, (UIViewController) -> ())
}

Stringはセクションや行のタイトルになります。

テーブルビューのための実装

UITableViewDelegateUITableViewDataSourceに準拠してテーブルビューのための実装をしていきます。
extensionでやったほうが後々見やすいです。

extension BootstrapViewController: UITableViewDelegate, UITableViewDataSource {
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return sections.count
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let section = sections[section]
        return section.1.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let row = sections[indexPath.section].1[indexPath.row]
        let cell = tableView.dequeueReusableCell(withIdentifier: "row", for: indexPath)
        cell.textLabel?.text = row.0
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        let row = sections[indexPath.section].1[indexPath.row]
        row.1(self)
    }
    
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return sections[section].0
    }
}

ここでミソなのは didSelectRowAt:のときに Rowで定義したブロック関数が呼ばれることです。
引数に自分自身(ViewController)を渡しています。

メインインターフェイスを変える

プロジェクト設定 TARGETS > (プロジェクト名) > General > Deployment Info > Main InterfaceBootstrap に変更します。
(ここは変更していなければMainになっていると思います)

使っていこう

ここまで下ごしらえができると、あとは使うだけです。

Sectionの配列をこのように定義してみます。

class BootstrapViewController: UIViewController {
    
    private typealias Section = (String, [Row])
    private typealias Row = (String, (UIViewController) -> ())
    
+   private let sections: [Section] = [
+       ("画面の確認", [
+           ("D画面", { bootstrapViewController in
+               let controller = // (D画面ViewControllerを生成する処理)
+               bootstrapViewController.present(controller, animated: true, completion: nil)
+           }),
+       ]),
+   ]
}

これを実行すると

こういう画面が表示されると思います。
これでセルをタップすると、D画面にタップ1回で画面遷移できるというわけです。

他にもサッと確認したい画面があれば、Rowの配列を増やしていきます。

class BootstrapViewController: UIViewController {
    
    private typealias Section = (String, [Row])
    private typealias Row = (String, (UIViewController) -> ())
    
    private let sections: [Section] = [
        ("画面の確認", [
            ("D画面", { bootstrapViewController in
                let controller = // (D画面ViewControllerを生成する処理)
                bootstrapViewController.present(controller, animated: true, completion: nil)
            }),
+           ("E画面を確認する", { bootstrapViewController in
+               let controller = // (E画面ViewControllerを生成する処理)
+               bootstrapViewController.present(controller, animated: true, completion: nil)
+           }),
        ]),
    ]
}

他にも

ここまでは画面確認について書きましたが、
ちょっとしたメソッドを作ったときの動作確認もやったりしています。

extension String {
    // 空文字列だとnilが返るメソッド
    func emptyToNil() -> String? {
        return isEmpty ? nil : self
    }
}
class BootstrapViewController: UIViewController {
    
    private typealias Section = (String, [Row])
    private typealias Row = (String, (UIViewController) -> ())
    
    private let sections: [Section] = [
        ("画面の確認", [
            ("D画面", { bootstrapViewController in
                let controller = // (D画面ViewControllerを生成する処理)
                bootstrapViewController.present(controller, animated: true, completion: nil)
            }),
            ("E画面を確認する", { bootstrapViewController in
                let controller = // (E画面ViewControllerを生成する処理)
                bootstrapViewController.present(controller, animated: true, completion: nil)
            }),
        ]),
+       ("メソッドの確認", [
+           ("emptyToNil()の確認", { _ in
+               print("'ABC'が返るはず \( "ABC".emptyToNil() )")
+               print("nilが返るはず \( "".emptyToNil() )")
+           }),
+       ]),
    ]
}

これで起動してタップして「あー、期待通り動いてるわー」というのを見ます。で、確認が終わったら消してしまいます。
この動作確認は残しておきたいなーと思ったらテストコードに改めて書き起こすというスタンスでやっています。

他には、ログイン・ログアウトなど状態を1タップで変えられるようにしておく・・・といったことも書けば便利になったりします。

        ("アカウント状態を変更する", [
            ("ゴールド会員になる", { _ in
                // (ゴールド会員にする処理をここに書く)
            }),
            ("シルバー会員になる", { _ in
                // (シルバー会員になる処理をここに書く)
            }),
            ("通常会員になる", { _ in
                // (通常会員になる処理をここに書く)
            }),
            ("ログアウト状態にする", { _ in
                // (ログアウト処理をここに書く)
            }),
        ]),

ここでDIなどでモックなどを挟めば、簡易的なテストができるかなーと思います。

まとめ

というわけで、個人アプリ作るときなどにいつも最初に採用している方法のご紹介でした。

アプリが大きくなってくるとすぐに肥大化してしまうので、
「もうモジュールとして完成したので削除しとくかー」とバッと消したり、
「バグが出たから直すまでもう一度足しておくかー」と足したり、
用途に応じてメンテナンスするのも大事です。

ではまた。

Discussion