はじめに
iOSアプリにおいてタブのUIを実装するためのAPIとして、UIKitではUITabBarController、SwiftUIではTabViewがそれぞれ提供されています。
タブUIにおいて、Apple標準アプリをはじめ多くのアプリではアクティブなタブをタップした際に次のような挙動がよく見られます。
- 画面遷移している場合はルートまで画面スタックを戻す
- 画面がスクロールされている場合は一番上までスクロールを戻す
UIKitのUITabBarControllerを利用すると一部の挙動は勝手に対応してくれるのですが、SwifUIのTabViewでは何もしてくれないため、現状では開発者が頑張る必要あります。
また一方、SwiftUIアプリのアーキテクチャとしてpointfreeco/swift-composable-architectureによるTCA(The Composable Architecture)に人気が集まっているようです。個人的には最近触り始めた段階になります。
そこで今回は、TCAを採用したアプリで前述のTabViewでアクティブタプをタップした際の挙動の実装方法を検討しました。
実装
こちらにデモアプリを用意しました。
前提としている環境は次の通りです。
- Xcode 15.3
- iOS 17
- SwiftUI x TCAアーキテクチャ
アプリではTabViewで2つのタブ(TabAとTabB)を実装しています。
どちらも前述の挙動を実現していますが、次のように若干異なる実装方法としています。
- TabA: 単純に一覧画面から詳細画面へドリルダウンして行く画面遷移を想定し、NavigationLinkを利用
- TabB: 何かしらのプレゼンテーションロジックを伴う画面遷移を想定して、Reducerを経由して画面遷移
ポイント
- 画面遷移をNavigationStackで管理する
- NavigationStackはiOS16から利用可能であり、画面遷移をある程度自由に制御できます
- 主に
pointfreeco/swift-composable-architecture
のCaseStudiesにあるサンプルコードを参考にしました
- TabViewにてアクティブなタブをタップされたイベントを検知するため、Bindingをインターセプトする
- コードはこの辺り
- [SwiftUI] タブがタップされた時に上までスクロールする。あるいはそれを共通化する話。の記事が大変参考になりました
- タブタップのイベントをNotificationCenterから通知し、それを
AsynStream<Void>
へ変換して監視する - タブを切り替えても状態を保持するため、Storeを@Stateで保持する
まとめ
とりあえずは目的の挙動を実現できているとは思いますが、割と複雑な実装になってしまってる感があり不安もあります。
他の方法など何かありましたらご意見いただけると嬉しいです。