$30 off During Our Annual Pro Sale. View Details »

SwiftUIの進化に ついていくためにやったこと

Recruit
PRO
August 31, 2023

SwiftUIの進化に ついていくためにやったこと

iOSDC Japan 2023 での中島の発表資料です。

Recruit
PRO

August 31, 2023
Tweet

More Decks by Recruit

Other Decks in Business

Transcript

  1. @motoshima1150
    iOSDC 2023
    SwiftUIの進化に
    ついていくためにやったこと

    View Slide

  2. このセッションのねらい
    『スタディサプリ 中学講座』がSwiftUIと共に歩んできた道を振り返りました。
    ポイント1 直面した課題の例として
    具体的にAlert, NavigationViewの進化を知る
    SwiftUIのような進化の早いフレームワークと
    どのように付き合うのかを考える
    ポイント2

    View Slide

  3. Agenda | 01
    02
    03
    04
    発表者紹介
    SwiftUIと『スタディサプリ 中学講座』
    SwiftUIの実践的な書き換え
    まとめ

    View Slide

  4. 発表者紹介
    01

    View Slide

  5. @motoshima1150 / 中島元成
    - iOS アプリエンジニア
    - 『スタディサプリ 中学講座』開発を担当
    - 🍺, 🏕, 󰴢
    "3dメガネ アイコン" by DesignBolts is licensed underCC BY 4.0

    View Slide

  6. (株)リクルートでの位置づけ
    創業
    本社
    事業の売上収益
    従業員数
    2012年 10月1日
    株式会社リクルートホールディングス設立時の
    分社化により設立
    2018年 4月1日
    株式会社リクルートに商号変更
    東京
    7,606億円
    (2022年4月1日~2023年3月31日)
    19,836人
    (2023年4月1日現在 / アルバイト・パート含)
    OUR VISION

    View Slide

  7. サービスラインナップ
    様々なサービスラインナップにて事業展開をしています
    スタディサプリ
    対象学年 小・中学校 高校 大学・社会人
    オンラインビデオ
    (B to C)
    オンラインビデオ&
    アセスメント
    (B to B to C)
    オンラインコーチング パーソナルコーチプラン

    View Slide

  8. SwiftUIと『スタディサプリ 中学講座』
    02

    View Slide

  9. SwiftUI 年表 と『スタディサプリ 中学講座』
    WWDC19 WWDC20 WWDC21 WWDC22 WWDC23
    SwiftUI発表
    SwiftUIベースのアプリ構築が可能に
    Listや検索の大幅強化
    .searchable modifierの追加
    .refreshable modifierの追加
    .swipeActions modifierの追加
    NavigationStackの追加
    Charts フレームワークの追加
    Photos フレームワークの追加
    KeyframeAnimation
    @Observable の追加
    MapKit の強化
    SwiftDataのサポート
    リニューアルリ
    リース
    リニューアル
    開始

    View Slide

  10. 『スタディサプリ 中学講座』はSwiftUIと共に歩んでいる

    View Slide

  11. どのような課題が発生したか
    ● 既存実装が非推奨となり、将来的に廃止されてしまう
    ○ Alert, NavigationLink
    ● 新規APIが公開されるが、対応バージョン要件を満たせず導入できない
    ○ NavigationStack
    Alert, Navigationのケースで書き換えをご紹介いたします

    View Slide


  12. 『スタディサプリ 中学講座』の構成
    スタディサプリ 中学講座 project
    Core: 依存関係や共通処理を持つ
    UIComponent: 共通UIやDesignSystem の token などを持つ
    JuniorHighSchool: 実際の画面単位でサブモジュールを用意
    MyPage
    StudyReport

    マルチモジュール構成に移行中。各モジュール間は独立した実装が可能になっています。
     コラム

    View Slide

  13. SwiftUIの実践的な書き換え
    03

    View Slide

  14. @State private var showAlert = false
    var body: some View {
    Button("Tap to show alert") {
    showAlert = true
    }
    .alert(isPresented: $showAlert) {
    Alert(
    title: Text("Current Location Not Available"),
    message: Text("Your current location can’t be " +
    "determined at this time.")
    )
    }
    }
    struct Login: View {すprivate var didFail = false
    let alertTitle: String = "Login failed."
    var body: some View {
    LoginForm(didFail: $didFail)
    .alert(
    alertTitle,
    isPresented: $didFail
    ) {
    Button("OK") {
    // Handle the acknowledgement.
    }
    }
    }
    }
    ● Alert viewが廃止となり、より扱いやすくなっている。
    Alert の進化
    iOS 13.0–17.0 Deprecated
    iPadOS 13.0–17.0 Deprecated
    https://developer.apple.com/documentation/swiftui/
    iOS 15.0+
    iPadOS 15.0+

    View Slide

  15. 『スタディサプリ 中学講座』でのAlert用途
    ● ユーザーに向けて通信エラーを伝える
    ● 学習を離脱するなどのユーザー状態が破棄される前の確認
    ● 強制バージョンアップやメンテナンスなどの強制力の強いユーザー告知

    View Slide

  16. Alert の書き換え
    .background(
    EmptyView().alert(
    isPresented: $isFirstAlertPresented,
    content: {
    Alert(title: Text("First alert"), message: Text("Alert message"))
    }
    )
    )
    .alert(
    "First alert",
    isPresented: $isFirstAlertPresented,
    actions: { },
    message: { Text("Alert message") }
    )
    旧APIでは1つのViewに対して、複数のAlert
    modifierを付与した際に表示されない動作の
    回避として利用

    View Slide

  17. NavigationView
    Navigation APIの進化
    iOS 13.0–17.0 Deprecated
    iPadOS 13.0–17.0 Deprecated
    iOS 16.0+
    iPadOS 16.0+
    @State private var isShowingPurple = false
    @State private var isShowingPink = false
    @State private var isShowingOrange = false
    var body: some View {
    NavigationView {
    List {
    NavigationLink("Purple", isActive: $isShowingPurple) {
    ColorDetail(color: .purple)
    }
    NavigationLink("Pink", isActive: $isShowingPink) {
    ColorDetail(color: .pink)
    }
    NavigationLink("Orange", isActive: $isShowingOrange) {
    ColorDetail(color: .orange)
    }
    }
    }
    .navigationViewStyle(.stack)
    }
    // Nothing on the stack by default.
    @State private var path: [Color] = []
    var body: some View {
    NavigationStack(path: $path) {
    List {
    NavigationLink("Purple", value: .purple)
    NavigationLink("Pink", value: .pink)
    NavigationLink("Orange", value: .orange)
    }
    .navigationDestination(for: Color.self) { color in
    ColorDetail(color: color)
    }
    }
    }
    https://developer.apple.com/documentation/swiftui/migrating-to-new-navigation-types
    NavigationStack

    View Slide

  18. 『スタディサプリ 中学講座』でのNavigation用途
    ● Push遷移(2−3階層)
    ● プログラムで制御された Push遷移
    ● 1画面から複数の種類の遷移先

    View Slide

  19. NavigationView → NavigationStack の書き換え
    NavigationView {
    CourseScreen()
    }
    struct CourseScreen: View {
    ...
    @State var isActive = false
    @State var selectedTopicID = ""
    var body: some View {
    ZStack {
    NavigationLink(isActive: $isActive) {
    TopicScreen(topicID: selectedTopicID)
    } label: {
    EmptyView()
    }
    VStack {
    ForEach(course.topicIDs, id: \.self) { id in
    Button("topic id: \(id)") {
    selectedTopicID = id
    isActive = true
    }
    }
    }
    }
    ...
    }
    }
    NavigationStack {
    CourseScreen()
    }
    struct CourseScreen: View {
    ...
    @State var isActive = false
    @State var selectedTopicID = ""
    var body: some View {
    VStack {
    ForEach(course.topicIDs, id: \.self) { id in
    Button("topic id: \(id)") {
    selectedTopicID = id
    isActive = true
    }
    }
    }
    .navigationDestination(isPresented: $isActive) {
    TopicScreen(topicID: selectedTopicID)
    }
    ...
    }
    }
    不自然なZStackとNavigationLinkが消えてスッキリ

    View Slide

  20. 運用状況

    View Slide

  21. 1. Issueを作成し、課題としてチーム内共有する。
    2. 動作確認の担保、書き換え方針を共有する。
    3. 品質改善の時間で実際にコードの書き換えを行う。
    計画

    View Slide

  22. ● 課題をオープンにすることで、以下のメリットを受けています。
    ○ 目につくので忘れない
    ○ いつでも見返せる
    ○ 対応時期のコミュニケーションが容易
    1. Issueを作成し、課題としてチーム内共有する。

    View Slide

  23. ● MagicPodを用いたE2Eテストを導入しています。
    ○ 画面遷移などの書き換えも安心して書き換えができます。
    ● 書き換えの方針などを事前共有によってチーム内で検証します。
    2. 動作確認の担保、書き換え方針を共有する。

    View Slide

  24. 私たちのチームでは品質改善に向けた実装時間が確保されています。
    ● Quality Budget
    ○ 2週間に1日品質改善に寄与する issueを消化する日を設けています。
    ● KAIZEN
    ○ 小中高プロダクトiOSチームでプロダクトを跨いでペアを組み、
    週に1回1時間程度品質改善に当てる。
    3. 品質改善の時間で実際にコードの書き換えを行う。

    View Slide

  25. ● 『スタディサプリ』は年に1回バージョン更新を行なっています。
    ○ 今年の4月にiOS 14をサポートバージョンから外しました。
    ○ シェア率を参照しながらPdMとすり合わせを行い更新しています。
    ● これから導入を控えているもの。
    今後の予定は
    https://developer.apple.com/documentation/swiftui/navigationstack
    https://developer.apple.com/documentation/swiftui/view/presentationdetents(_:)

    View Slide

  26. まとめ
    04

    View Slide

  27. 私たちはSwiftUIの進化を追いながら、落ち着いて取捨選択ができたと思います。
    書籍「進化的アーキテクチャ」と照らし合わせ要因を振り返りました。
    要因
    ● 年一回のサポートバージョンの見直しタイミングがある
    ○ 腐敗防止層の対策が立てやすい
    ● モジュール分割により、新しいコードは新しい設計で書くことができる
    ○ 漸進的な改善を行うことができる
    ● メインタスクとは別で品質改善のために取り組む時間がある
    ○ 心理的にも余裕が生まれる
    運用を通しての振り返り

    View Slide

  28. 最後に『スタディサプリ 中学講座』で得たまなび
    私たちのSwiftUIという選択は、2020年時点では将来性のリスクを取ることだったと思
    います。
    導入時点ではどれも予測のつかない変化でしたが、その点で過剰に忌避するのでは
    なく、ワークアラウンドな対応なども受け入れながらSwiftUIの進化に合わせて一緒に
    進化しつつ、導入タイミングはプロダクトでハンドリングできる基盤を整えておくことの
    重要性を改めてまなぶことができました。

    View Slide

  29. その他 弊社についてのご紹介
    以下リンク先でもプロダクト開発部について紹介しています。ぜひご覧ください。
    ・スタディサプリ プロダクトチーム ブログ
     スタディサプリ Product Team Blog
     発表資料 - スタディサプリ Product Team Blog
     ※エンジニア / デザイナー / TPMが記事を書いています
    ・ブランドサイト
     スタディサプリ BRAND SITE
    ・採用ページはこちら
      キャリア | スタディサプリ BRAND SITE
    ・カジュアル面談はこちらからお気軽にご応募ください
      スタディサプリエンジニア職種カジュアル面談エントリーフォーム

    View Slide

  30. 一緒に事業拡大を目指す仲間を
    お待ちしています!

    View Slide

  31. ご清聴ありがとうございました

    View Slide