SwiftUI導入に向けての取り組み
こんにちは!
株式会社ココナラアプリ開発グループ、iOSチームの上田です。
ココナラはニックネームで呼び合う文化があり、皆からはうえぽんと呼ばれています。
主にやっていることはチーム名どおりココナラのiOSアプリの開発を行っていて、タスクによってはAndroidの開発も行っていたりします。
今回はココナラのiOSアプリにSwiftUIを導入するまでに検討したこと等を共有していきたいと思います!
なぜSwiftUIを導入しようと思ったか
SwiftUIを導入しようと思った理由はいくつかありますが、主に下記の理由です。
- 開発速度向上
- 採用への影響
開発速度向上
まずはSwiftUI導入によって期待する開発速度向上についてです。
現状の課題
ココナラのiOSアプリは2014年12月にリリースしており、現在のココナラのレイアウトはStoryboardで実装しています。
最初期にリリースされた画面は1つのStoryboardに複数の画面が定義されており、開くだけでXcodeがちょっと固まるので、Storyboardを開く・編集するのが辛い状態になっています。
(これくらいだったらまだ大丈夫かな...)
途中からViewControllerとStoryboardは1:1の関係で実装するルールが定義され、新しい画面に関しては上記の問題はある程度緩和されたものの、コードレビューでStoryboardの差分を見るのは辛い物があります。
SwiftUIで改善すること
これらの「辛い」をSwiftUIで解決したいと思っています。
SwiftUIは宣言的UIで、コードベースでレイアウトを組んでいきます。
コードでレイアウトを定義するのでレビューがしやすく、Storyboardとは違い、開いたりレイアウトを編集するだけでXcodeが固まるという事はありません。
また、SwiftUIにはプレビュー機能が存在しています。
レイアウトのコードを編集すると、プレビューの方のレイアウトも動的に変更が加わるのですぐに実際の表示を確認する事が出来ます。
これにより、レイアウト変更をした時に都度ビルドして、表示が正しいかどうかの確認をするという手間が無くなりました。
採用への影響
次に採用への影響です。
エンジニアだと新しい技術が好きな方も多く、日々技術も新しい物が登場してきます。
iOSエンジニアの方だと、Objective-CからSwift移行を思い浮かべる方が多いのではないでしょうか?
自分はiOS開発はObjective-Cからやっていますが、Swiftをある程度学んでからObjective-Cを触ると凄い数の[]
や@
に「なんだこれ...Swiftの方がめちゃくちゃ書きやすいじゃん!」と思ったのを今でも覚えています。
就職・転職活動される方の中には、Swiftが出て数年経っているのに新規ファイルもまだObjective-Cを採用していると少し抵抗がある方も居たのではないでしょうか?
実際に自分がココナラに転職する時に行っていた転職活動時には、新規実装もObjective-Cで行っている会社は候補からは外していました。
それと同じような事がUIKitからSwiftUIへの移行で起きると思います。
最近では他社のエンジニアさんとお話しても大体が「新規実装はSwiftUIで実装しているよ」という方が多く、徐々にSwiftUIの割合が増えてきています。
そのように、新規開発でSwiftUIを使ってないからと言う理由でココナラを転職先候補として外されるのを避けたいという思いもあり、SwiftUIを導入するきっかけの1つとなりました。
また、新しい技術を導入する事で既存メンバーのモチベも上がりますし、採用の観点で、今ある技術だけで十分と言う人よりも継続的に自分のスキルを更新していける方を採用出来るというメリットもあります。
SwiftUI導入で検討したこと
次にSwiftUI導入で検討したことについて話していきます。
導入にあたって考えたことは主に下記の通りです。
- サポートするiOSバージョン
- アーキテクチャ
- チームに展開
サポートするiOSバージョンの検討
まず一番初めに検討したのがサポートするiOSバージョンでした。
結論から、ココナラはSwiftUI導入に伴い、iOS14以上をサポートする事にしました。
今のココナラはiOS13以上をサポートしており、SwiftUIはiOS13から登場したので入れようと思えばそのまま入れることも可能でした。
しかし、iOS13ではLazyStackが使えなかったり、Grid表示が対応してなかったり、複雑なレイアウトを組もうとすると結局iOS13ではUIKitをラップする必要があったり...、とそのまま使うにはかなりOS間の差分を吸収する必要があり、ココナラのプロジェクトに導入するのには耐えられないと判断しました。
そうなるとiOS13を切る必要があります。
とはいえ、ココナラはtoC, toBと幅広いユーザーが利用しています。
サポートバージョンを切り上げると言う事は、そのバージョンを利用しているユーザーを逃す可能性があると言う事です。
まずは、iOS13を利用している方がどれくらい居るのか、その方達の取引金額がどれくらいあるのか等、他の部署と確認しながら進めていきました。
また、他社の最低サポートOSがいくつなのかを確認し、ココナラも最低サポートOSを14に引き上げても問題ないという判断で最低サポートを14に引き上げる事に決めました。
アーキテクチャの検討
次に検討したのがSwiftUIを導入した時のアーキテクチャです。
現在のココナラのアーキテクチャはMVVM + CleanArchitectureを採用しています。
現在のアーキテクチャ
レイアウト周りは、ViewModelはInputとOutputが定義されており、ViewControllerからユーザー操作をViewModelのInputに通知し、ViewControllerで表示する内容をViewModelのOutputから受けとって表示するという流れになっています。
SwiftUI導入後も今と変わらずMVVM + CleanArchitectureを採用する予定です。
移行後のアーキテクチャ
既存のアーキテクチャを使い回す理由は下記の通りです。
- ViewControllerをSwiftUIに変更し、ViewModelのInputとOutputをSwiftUI用に変えるだけで移行が出来る
- UseCase以降は既存の実装を使いまわせる
- 以前MVPからMVVMに移行した経緯があり、既に2つのアーキテクチャが混在しているので、これ以上新しいのを追加するとココナラに入った時の学習コストが高くなる
SwiftUI導入後の変更点は主に2箇所で、ViewControllerがSwiftUIに変わるのと、それに伴ってViewModelの中身がSwiftUI用に変わります。
現在のViewControllerとViewModel
class HogeViewController: UIViewController {
@IBOutlet var tableView: UITableView!
private let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
bind()
}
private func bind() {
let output = viewModel.transform(
input: HogeViewModel.Input(
viewWillAppear: rx.sentMessage(#selector(viewWillAppear(_:))).mapToVoid().asDriverOnErrorJustComplete()))
// テーブルデータを表示
output.tableData
.drive(tableView.rx.items(cellIdentifier: HogeCell.identifier, cellType: HogeCell.self)) { _, hoge, cell in
cell.configureCell(hoge: hoge)
}.disposed(by: disposeBag)
}
}
class HogeViewModel {
private let useCase: HogeUseCaseProtocol
init(...) {
...省略
}
// ユーザー操作
struct Input {
let viewWillApper: Driver<Void>
}
// 表示内容
struct Output {
let tableData: Driver<[HogeModel]>
}
// 必要に応じて画面表示に必要なデータを加工し、Outputとして返す
func transform(input: Input) -> Output {
let tableData = input.viewWillAppear
.flatMap {
return useCase.getData()
...省略(APIを叩いてデータ取得等)
}
return Output(tableData: tableData)
}
}
SwiftUI移行後のSwiftUIとViewModel
現在検討段階でリリースまでに変わる可能性がありますが、下記のような構成を想定しています。
struct ReportViolationScreen: View {
@ObservedObject var viewModel = HogeViewModel()
var body: some View {
ScrollView {
VStack(spacing: 0) {
ForEach(viewModel.tableData, id: \.self) { data in
HogeDataView(data: data)
}
}
}
.onAppear {
viewModel.onAppear()
}
}
}
class HogeViewModel: ObservableObject {
private let useCase: HogeUseCaseProtocol
// MARK: - Output
@Published tableData: [HogeModel] = []
init(...) {
...省略
}
// MARK: - Input
func onAppear() {
getData()
}
private let getData() {
useCase.getData()
.do(onNext: {[weak self] data in
self?.tableData = data
}).disposed(by: disposeBag)
}
}
UseCase以降に関しては変更がなく今までと同じ書き方で書けるので、既存の画面をSwiftUIに置き換える場合でもViewControllerとViewModelの変更のみで移行する事が出来ます。
かなりざっくりとした説明ですが、この辺りはSwiftUIリリース後の記事で詳しく書ければなと思います。
チームへの展開
次にチームへの展開です。
iOSチームは5人いますが、皆がSwiftUIの経験があるわけではなく、SwiftUIを導入するとなると皆が最低限実装出来るように展開する必要があります。
ここは現在検討段階ですが、一旦各々AppleのSwiftUIチュートリアルをやって貰おうと思っています。
また、UdemyにもSwiftUIのコースがあり、このあたりをやっても良いんじゃないかな?という話が出ています。
このチームに展開するために作成したドキュメントは、新卒の子が入ってきた時や、中途でSwiftUIを触った事がない方向けのオンボーディングとしても利用出来るようにしたいと思っています。
チームでSwiftUIを触った事がない人にAppleのドキュメントを触ってもらいながら、実際の業務でつまづいた事等をヒアリングしながら、ココナラのSwiftUIのドキュメントを作成していきたいと思います。
まとめ
まだSwiftUIを導入したバージョンのリリースはこれからですが、リリースに向けての取り組みを簡単に紹介しました!
実際にSwiftUIでレイアウトを作っていると、やっぱりコードレビューもやりやすいし、Xcodeも固まらないし、SwiftUI良いな!と思いました。
これからSwiftUIを導入したプロジェクトが走り出すのですが、実際にSwiftUIを導入した後にいろいろと課題が出てくると思います。
なので次回はSwiftUI導入後と運用で感じた課題などをまた共有したいと思います!
SwiftUIを使ってiOSアプリを開発したい!と言う方、ぜひ以下フォームよりお気軽にご連絡ください!
ココナラに少しでも興味が湧いたという方も大歓迎です。
入力時間は1、2分です。
ココナラのエンジニアについてもっと知りたい!という方はこちらをご確認ください。
Discussion