iOSのデザイン周りの開発と改善について🧸

f:id:jmty_tech:20210107095931p:plain:w300

ジモティーエンジニア紅一点の@chanNaruです🧸

iOSチームで開発を行っています。

軽い自己紹介

絵や漫画を描いたり車でドライブすることが好き、 個人でアプリを作ったりなども f:id:jmty_tech:20210107094156j:plain:w110f:id:jmty_tech:20210107094420p:plain:w110f:id:jmty_tech:20210107094448j:plain:w110f:id:jmty_tech:20210107094451j:plain:w110f:id:jmty_tech:20210107094454j:plain:w110 f:id:jmty_tech:20210107095812p:plain:w200

-> wantedly:iOSDCにてデザイン関連の登壇などをしたり、個人アプリの紹介など書いてます

-> Qiita:イラストあれば理解しやすいよねをモットーにたまーに書いてます(最近書いてない..)

話すテーマ 🌼 iOSのデザイン周りの開発について

好きなことが講じてデザイン周りにも興味があるということで 今回はまだまだ課題のあるiOSチームのデザイン周りの開発について、 具体的には私が入社してからの約2年間でどのような改善があったのかについて書こうと思います🧸

入社当時

もっとデザイナーやディレクターと連携できるような環境に身をおきたいとジモティー に転職をしたのですが、

「前職でできなかったデザイン周りの開発改善やるんだ!🔥」

だったり、

「(転職当時)今アツいAtomicDesign取り入れたいな〜〜〜」

という野望を持っていました。

実際入社した時には丁さんの記事でも書かれてましたが CleanArchitectureを導入するなど設計面の見直しが進められているなか、

  • デザイナーとの意思疎通がとりづらい
  • 画面とパーツが完全に依存している

などといったデザイン面の課題があり、

この課題の結果として

  • (1) 類似パーツを量産するコスト
    • 各パーツが画面依存しているので同じようなパーツを量産してしまっている
  • (2) 変更による1つ1つの改修コスト
    • デザイン変更によって類似しているパーツを1つ1つ修正しなければならない
  • (3) 既存と同じでとなった際のコスト
    • 「xx画面と同じ表示で!」などとなった時にその既存の作りを確認し真似しなければならない
  • (4) どこでどのようなパーツが使われているのか探すコスト
    • どんなパーツがどこでどのように使われているのかコードを探すのが大変

などといった負が誘発されることで非常に効率の悪い開発が行われていました。

f:id:jmty_tech:20210107095931p:plain:w500

f:id:jmty_tech:20210107095953p:plain:w600

↑定期的に行われる微妙な色が違うよ指摘..😭や、 f:id:jmty_tech:20210107100045p:plain:w300

↑ここの実装どうなってたっけ..😭などなど。


これらの課題を解決するために、以下の3つの開発見直しを行いました。

①cellの分解

特に改善したポイント: f:id:jmty_tech:20210107100611j:plain:w500

1つの画面のxibにパーツが全部組み込まれているため、その画面からしか使用できず他画面に同じ表示を追加する場合もそれを流用できないような作りになっていました。 その結果、何度も同じパーツを画面ごとに作るコストが発生。

(※ xib(≒storyboard):画面やパーツを作るときに作成するGUIのファイル)

これについては、

1画面=1xib(必要なパーツを全て持っている)
↓
1画面=1xib+α xib(適宜必要なパーツを参照する)

f:id:jmty_tech:20210107100633p:plain:w500

に作りを変えることで「画面とパーツが完全に依存している」状態を改善し、 毎回作り直さなければならないコストを減らしました。

(この課題の副作用として2019年のiOSDCの登壇でもネタにしたのですが、 特に投稿画面はパーツが多すぎることでxibを開いても何も表示できなかったりフリーズしたりなどという問題もあったので この辺りも開発効率改善にすごく寄与しました😭)

f:id:jmty_tech:20210107100736p:plain:w600

②cellの共通化

特に改善したポイント:

f:id:jmty_tech:20210107100754j:plain:w500

「①cellの分解」を行ったことで、「画面とパーツが完全に依存している」状態は改善できましたが さらにもっと汎用性がでるようにコードの改修を行いました。

具体的には以下のように、共通パーツを呼び出すためのprotocolを定義し各画面のテーブル用のリストを作る際に用いることで、 どの画面でも好きなように呼び出して扱えるcellにしました。

private var tableViewData: HogeTableViewData?

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    guard let tableViewData = self.tableViewData else {
        return UITableViewCell()
    }
    let cell = tableView.dequeueReusableCell(withIdentifier: tableViewData.get(indexPath.row).cellId,
                                                 for: indexPath)
    switch viewData {
        case let data as CommonCellViewDataPtcl:
            (cell as? CommonCell)?.setUp(viewData: data)
        default:
            break
    }
    return cell
}

*********************

struct HogeTableViewData {
    private var list: [HogeViewData]
    init(list: [HogeViewData]) { self.list = list }
}

protocol HogeViewData {
    var cellId: String { get }
    /**/
}

struct HogeViewData: HogeViewData, CommonCellViewDataPtcl {
    let cellId: String = R.nib.hogeCell.name
    let title: String = "たいとる"
    /**/
}

protocol CommonCellViewDataPtcl {
    var title: String { get }
    var backgroundColor: UIColor { get }
}

extension CommonCellViewDataPtcl {
    var backgroundColor: UIColor { return .white }
}

class CommonCell: UITableViewCell {
    private var viewData: HogeViewData?

    func setUp(viewData: CommonCellViewDataPtcl) { /**/ }
}

このようにすることで既存と同じようなデザイン指示があった際に、 使いたいcellのprotocolを呼び出すだけでパーツを流用できるようになりました。

変更があった際にもその1つのcellクラスを修正するだけで完了できますし、 また、上記ではbackgroundColorをextension化することで、「配置とかは同じだけど背景色が違う」などといった 「同じだけど微妙に違うから共通化しにくい」にも対応する形で汎用性をもたせています。

({ return .white }と初期値を設定しておくことで、意図しない変更にも強い作りになっています)

共通パーツについては、 類似パーツの量産を防ぎ、どんな共通パーツがあるのかを探しやすくするための工夫として、Zeplinを使ってカタログ化しています🧸 f:id:jmty_tech:20210107100818p:plain:w600

(※ Zeplin:デザインリソースから指示書やスタイルガイドなどを自動で生成するソフトウェアで俗に言う「デザイン共有ツール」。 ジモティーではこちらのパーツの一覧管理用ににのみ使用しています。 カンプなどはFigmaを使っています。)

導入タイミング・キッカケとしては、大きなprjで7画面ほど新しい画面を作ることになった際に毎回作るコストを減らすために考えられました。

このコードの課題としては、cellForRowAt内でviewDataPtclの分岐を忘れてしまうと、setupが呼ばれずエラーにならず空セルとなって表示されてしまう点があり、 ジモティーでは結合テストでその問題が起きないように担保しています。

データと画面を依存させずにこの問題をテスト以外で担保できないかなど含め、コードの精査を今後も続けていく予定です✊

③ラベル/ボタン/カラーレベルのコンポーネント

特に改善したポイント: f:id:jmty_tech:20210107100941j:plain:w500

ボタンやラベルでもデザイナーの意図しない色やサイズになっていたり、 既存を参考にしようにもどこでどう定義されているのか探すのが大変という課題に対して、 一番小さい単位のパーツ類をコンポーネントとして定義して各所で使うようにしました。

f:id:jmty_tech:20210107101000p:plain:w600

デザイナーチームではAtomicDesignという概念を既に使用していたので倣う形で以下のような資料をもとにデザイナーと打ち合わせを行ない、 そこそこ前に書いた記事なので現在と一部異なりますが、

【swift】コンポーネントを実装してパーツのスタイルを使いまわそう!😊【デザイン】

f:id:jmty_tech:20210107101030p:plain:w200

ここに書いている実装をプロジェクトに導入しました。

// ボタン:XLarge
@IBDesignable class XLargeButton: UIButton {
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        self.layer.cornerRadius = 2.0
        self.layer.borderWidth = 1.0
        self.titleLabel?.font = UIFont.largeButtonTitle()
    }

    // 背景塗りつぶしパターン
    func primaryFill(title: String = "") {
        if title.isPresent {
            self.setTitle(title, for: .normal)
        }
        self.layer.borderColor = R.color.jmtyColor()!.cgColor
        self.backgroundColor = R.color.jmtyColor()
        self.setTitleColor(R.color.invertedTextColor(), for: .normal)
    }
}

※Colorについては最近コードとパレットの二重管理からxcassetsの一元管理化に変えています

このような定義を行うことでスタイル名の「共通言語」が生まれ、 カンプ共有ツール上でスタイルの指示をしてもらうだけでコンポーネントを流用しデザインに反映するという効率アップだったり、 どこでどのようにスタイルが使われているのかをプロジェクトから探しやすくなり、 修正工数もグンと削減することができました👏

f:id:jmty_tech:20210107101102p:plain:w600

もともとデザイナーチームでもエンジニアとのこれらに関する意思疎通がうまくできていないという課題があったため一緒に検討を進められたのはとても良かったです👍

まずはエンジニア側で現状の実装を元に仮組みで導入し、ある程度うまく実装にのった段階で本格的にデザイナーとルールやパーツ選定を行うという進め方をしたことで、 デザイナーと計画してから導入までの期間を短く出来、軌道に上手くのせることができたのではと思っています。

結果

上記の見直しを行ったことで、

  • 大凡UI周りの開発について工数の半分ほどを削減🎉
  • パーツの大量発生を防ぐことでプロジェクト自体のサイズの増大を防げる
  • 既存実装を探す手間を防ぐ

などの改善がありました。

終わりに

上記で書いたこともまだまだ導入を進めているところで、Objective-Cのコードもまだ残っていたり課題は他にも色々あります。

少しでも興味を持っていただいたり、こういうアイデアもあるのではという方がおりましたらお話ししたいです。

+.🧸ジモティーではエンジニアを募集しています🧸+.