🍎

[SwiftUI][TCA] Alert/Dialog

2022/05/18に公開

概要

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

前回の様々なStateの使い方についてはこちら

今回扱うファイル

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

AlertsAndConfirmationDialogs

表示するalertとconfirmationDialogでカウントが出来るようになっています。

State,Action

今回のAlertStateConfirmationDialogStateは、
このライブラリに用意されており、
ボタンをタップしたときのアクションの送信を自動的に処理するので、
双方向バインディングやアクションクロージャーではなく、
リデューサーの中でその機能を適切に処理することができます。
またここでのポイントとしては、
AlertStateConfirmationDialogStateも、
OptionalStateであることがポイントとなります。

struct AlertAndConfirmationDialogState: Equatable {
  var alert: AlertState<AlertAndConfirmationDialogAction>?// OptionalState
  var confirmationDialog: ConfirmationDialogState<AlertAndConfirmationDialogAction>?// OptionalState
  var count = 0
}

enum AlertAndConfirmationDialogAction: Equatable {
  case alertButtonTapped
  case alertDismissed
  case confirmationDialogButtonTapped
  case confirmationDialogDismissed
  case decrementButtonTapped
  case incrementButtonTapped
}

Reducer

各Actionがコールされた際に、
alertconfirmationDialogStateの更新では、
.initで表示したい内容を設定出来ます。
まずはAlertStateから見ていきます。

AlertState

https://pointfreeco.github.io/swift-composable-architecture/AlertState/
titleやmessage、Buttonなどの設定方法は以下のコードの通りです。

case .alertButtonTapped:
    state.alert = .init(
      title: .init("Alert!"),
      message: .init("This is an alert"),
      primaryButton: .cancel(.init("Cancel")),
      secondaryButton: .default(.init("Increment"), action: .send(.incrementButtonTapped))
    )

またAlertをから戻る際の挙動では、
alertDismissedというアクションを送ることで、
Alertが消えるようになります。

// View
.alert(
      self.store.scope(state: \.alert),
      dismiss: .alertDismissed
    )

// Reducer
 case .alertDismissed:
    state.alert = nil
    return .none

続いてConfirmationDialogStateを見ていきます。

ConfirmationDialogState

https://pointfreeco.github.io/swift-composable-architecture/ConfirmationDialogState/
titleやmessage、Buttonなどの設定方法は以下のコードの通りです。

  case .confirmationDialogButtonTapped:
    state.confirmationDialog = .init(
      title: .init("Confirmation dialog"),
      message: .init("This is a confirmation dialog."),
      buttons: [
        .cancel(.init("Cancel")),
        .default(.init("Increment"), action: .send(.incrementButtonTapped)),
        .default(.init("Decrement"), action: .send(.decrementButtonTapped)),
      ]
    )
    return .none

Dialogから戻る際の挙動では、
confirmationDialogDismissedアクションを送り、
Dialogが消えるようになります。
基本的にはAlertと同じような処理となります。

// View
.confirmationDialog(
      self.store.scope(state: \.confirmationDialog),
      dismiss: .confirmationDialogDismissed
    )

// Reducer
case .confirmationDialogDismissed:
    state.confirmationDialog = nil
    return .none

View,Store

struct AlertAndConfirmationDialogView: View {
  let store: Store<AlertAndConfirmationDialogState, AlertAndConfirmationDialogAction>

  var body: some View {
    WithViewStore(self.store) { viewStore in
      Form {
        Section(header: Text(template: readMe, .caption)) {
          Text("Count: \(viewStore.count)")
          Button("Alert") { viewStore.send(.alertButtonTapped) }
          Button("Confirmation Dialog") { viewStore.send(.confirmationDialogButtonTapped) }
        }
      }
    }
    .navigationBarTitle("Alerts & Confirmation Dialogs")
    .alert(
      self.store.scope(state: \.alert),
      dismiss: .alertDismissed
    )
    .confirmationDialog(
      self.store.scope(state: \.confirmationDialog),
      dismiss: .confirmationDialogDismissed
    )
  }
}

Discussion