🍎

[SwiftUI][TCA] refreshable

2022/05/24に公開

概要

この記事ではTCA初心者の筆者が理解を深めていくために、
pointfreeco公式のサンプルアプリを基に理解しやすく整理していきます。
今回はrefreshableの使用例を整理して理解していきます。
前回の記事はこちら

今回扱うファイル

今回は公式サンプルの以下のファイルです。
https://github.com/pointfreeco/swift-composable-architecture/blob/main/Examples/CaseStudies/SwiftUICaseStudies/02-Effects-Refreshable.swift

Refreshable

基本的には前回記事のBasicsと同じ機能ですが、
APIコールの契機がボタンタップから、
画面を下スワイプによる画面更新(refreshable)になっています。
またそのrefreshableに対してCancel機能もあります。

State,Action

今回のRefreshableでのポイントは、
アクションにあるcase refreshのActionコールの契機の部分となります。
Viewの部分で詳しく整理します。

struct RefreshableState: Equatable {
  var count = 0
  var fact: String?
  var isLoading = false
}

enum RefreshableAction: Equatable {
  case cancelButtonTapped
  case decrementButtonTapped
  case factResponse(Result<String, FactClient.Error>)
  case incrementButtonTapped
  case refresh
}

Environment

前回のBasicsの内容と全く変更はありません。

Reducer

isLoadingをtrueにしAPIコール処理をしています。
またcancellableの処理もあります。

 case .refresh:
    state.fact = nil
    state.isLoading = true
    return environment.fact.fetch(state.count)
      .delay(for: .seconds(1), scheduler: environment.mainQueue.animation())
      .catchToEffect(RefreshableAction.factResponse)
      .cancellable(id: CancelId())

View,Store

まずアクションrefreshの契機として、
refreshableの中で記載があります。
Actionを送るsendメソッドのパラメータに今回は、
whileを使用しています。

struct RefreshableView: View {
    let store: Store<RefreshableState, RefreshableAction>

    var body: some View {
      WithViewStore(self.store) { viewStore in
        List {
          Text(template: readMe, .body)

          HStack {
            Button("-") { viewStore.send(.decrementButtonTapped) }
            Text("\(viewStore.count)")
            Button("+") { viewStore.send(.incrementButtonTapped) }
          }
          .buttonStyle(.plain)

          if let fact = viewStore.fact {
            Text(fact)
              .bold()
          }
          if viewStore.isLoading {
            Button("Cancel") {
              viewStore.send(.cancelButtonTapped, animation: .default)
            }
          }
        }
        .refreshable {
	  // ポイント
          await viewStore.send(.refresh, while: \.isLoading)
        }
      }
    }
  }

send(_:while:)

以下公式のドキュメントになります。
https://pointfreeco.github.io/swift-composable-architecture/send(_:while:)
今回の場合ではsend(_:while:)メソッドを使用して、
isLoading状態がtrueである間、中断しています。
その状態がfalseに戻ると、このメソッドは再開し、
.refreshableに作業が終了したことを通知し、インジケータを消します。

public func send(
      _ action: Action,
      while predicate: @escaping (State) -> Bool
    ) async {
      self.send(action)
      await self.suspend(while: predicate)
    }

次回

3章としてNavigationを使用した場合のサンプルをまとめていきます。
記事はこちら

Discussion