The Composable Architecture(TCA)のサンプルから画面遷移の実装方法について学びます。
今回扱うサンプルは以下の3つです。
Stateがnilとnon-nilのときで画面表示を切り替える
画面遷移の実装に入る前に、Stateがnilとnon-nilなときに画面表示を切り替える方法について説明します。
01-GettingStarted-OptionalState
の画面は以下のようになっています。
「Toggle counter state」ボタンをタップすると、カウンタービューの表示・非表示が切り替わります。
この仕組みはIfLetStoreを使うことで実現されています。
struct OptionalBasicsState: Equatable { // [1] var optionalCounter: CounterState? } // [2] IfLetStore( self.store.scope( state: \.optionalCounter, action: OptionalBasicsAction.optionalCounter ), then: { store in VStack(alignment: .leading, spacing: 16) { Text(template: "`CounterState` is non-`nil`", .body) CounterView(store: store) .buttonStyle(BorderlessButtonStyle()) } }, else: { Text(template: "`CounterState` is `nil`", .body) } )
[1]
本サンプル画面のStateはCounterState?型のプロパティを持っています。
[2]
IfLetStoreにオプショナルなStateの型を持つStoreを渡します。ここではCounterState?型のStateを持つStoreを渡しています。
then:
にはStoreが持つStateがnon-nilであるときに表示されるビューを指定します。
else:
には反対にStateがnilであるときに表示されるビューを指定します。
IfLetStoreのドキュメントには、主に2つの用途で使用すると記載があります。
- 上記のようにStateが存在するかどうかでビューを切り替える
- 画面遷移時にビューを切り替える
2についてはこのあと見ていきます。
画面遷移したあとデータをロードする
03-Navigation-NavigateAndLoadの画面は以下のようになっています。
「Load optional countsaer」をタップすると画面遷移アクションが実行され、同時にデータのロードが実行されます。
1秒後にデータのロードが完了し、カウンタービューが表示されます。
実装で注目すべきはこのあたりです。
NavigationLink( // [1] destination: IfLetStore( self.store.scope( state: \.optionalCounter, action: NavigateAndLoadAction.optionalCounter ), then: CounterView.init(store:), else: { ActivityIndicator() } ), // [2] isActive: viewStore.binding( get: \.isNavigationActive, send: NavigateAndLoadAction.setNavigation(isActive:) ) ) { HStack { Text("Load optional counter") } } // Reducerの実装 switch action { case .setNavigation(isActive: true): state.isNavigationActive = true return Effect(value: .setNavigationIsActiveDelayCompleted) .delay(for: 1, scheduler: environment.mainQueue) .eraseToEffect() case .setNavigation(isActive: false): state.isNavigationActive = false state.optionalCounter = nil return .none case .setNavigationIsActiveDelayCompleted: state.optionalCounter = CounterState() return .none
[1]
NavigationLinkのイニシャライザのdestination:
引数にIfLetStoreが指定されています。
「Load optional counter」ボタンがタップされると画面遷移してdestination:
に指定されたビューが表示されるのですが、IfLetStoreが指定されているため、optionalCounter
がnilの間はelse:
に指定されているインジケータが表示され、non-nilになったらthen:
に指定されているCounterViewが表示されるという動きになります。
[2]
isActive:
引数にはBindingを指定しています。
このBindingは次の画面への遷移が実行されると.setNavigation(isActive: true)
を送信し、元の画面に戻る遷移が実行されると.setNavigation(isActive: false)
を送信します。
全体の動きをまとめると以下のようになります。
- 「Load optional counter」ボタンがタップされると
.setNavigation(isActive: true)
が送信され、画面遷移する - 画面遷移直後はまだ
optionalCounter
がnilであるため、インジケータが表示される - 1秒後、
setNavigationIsActiveDelayCompleted
が送信され、optionalCounter
がnon-nilになる optionalCounter
がnon-nilになったので、CounterViewが表示される
データをロードしたあと画面遷移する
03-Navigation-LoadThenNavigateはデータをロードしてから画面遷移する例です。
「Load optional counter」ボタンをタップすると同時にデータのロードが開始され、インジケータが表示されます。
データのロードが完了したら画面遷移します。
実装で注目すべきはこのあたりです。
NavigationLink( // [1] destination: IfLetStore( self.store.scope( state: \.optionalCounter, action: LoadThenNavigateAction.optionalCounter ), then: CounterView.init(store:) ), isActive: viewStore.binding( get: \.isNavigationActive, send: LoadThenNavigateAction.setNavigation(isActive:) ) ) { HStack { Text("Load optional counter") if viewStore.isActivityIndicatorVisible { Spacer() ActivityIndicator() } } } // Reducerの実装 switch action { case .setNavigation(isActive: true): state.isActivityIndicatorVisible = true return Effect(value: .setNavigationIsActiveDelayCompleted) .delay(for: 1, scheduler: environment.mainQueue) .eraseToEffect() case .setNavigation(isActive: false): state.optionalCounter = nil return .none case .setNavigationIsActiveDelayCompleted: state.isActivityIndicatorVisible = false state.optionalCounter = CounterState() return .none
[1]
さきほどとの違いはIfLetStoreでelse:
引数を指定していないことです。
こうすることで、optionalCounter
がnilの間は画面遷移が実行されません。
全体の動きをまとめると以下のようになります。
- 「Load optional counter」ボタンがタップされると
.setNavigation(isActive: true)
が送信され、isActivityIndicatorVisible
がtrueになりインジケータが表示される - 1秒後、
setNavigationIsActiveDelayCompleted
が送信され、optionalCounter
がnon-nilになる。また、isActivityIndicatorVisible
がfalseになりインジケータが非表示になる optionalCounter
がnon-nilになったので、画面遷移する
まとめ
オプショナルなStateでビューを切り替える方法と、画面遷移の実装方法について説明しました。
どれもアプリを実装する上でよく出てくるユースケースなので、今回学んだ内容は頻繁に実装していくことになると思います。