Jan 8, 2024

Present an alert using TCA

Alert with custom actions

Here's how you can present a SwiftUI alert using the Composable Architecture.

import ComposableArchitecture
import SwiftUI

@Reducer
struct ShowAlertFeature {
  @ObservableState
  struct State: Equatable {
    @Presents var alert: AlertState<Action.Alert>?
  }
  
  enum Action: BindableAction {
    enum Alert {
      case tappedRetry
    }
    case alert(PresentationAction<Alert>)
    case binding(BindingAction<State>)
    
    case showAlert
  }

  var body: some Reducer<State, Action> {
    BindingReducer()
    Reduce { state, action in
      switch action {
      case .alert(.presented(.tappedRetry)):
        // Execute logic
        return .none
      case .showAlert:
        // Showing an alert
        state.alert = AlertState {
          TextState("Could not load")
        } actions: {
          // This will dismiss the alert
          ButtonState(role: .cancel) {
            TextState("OK")
          }
          ButtonState(action: .tappedRetry) {
            TextState("Retry")
          }
        } message: {
          TextState("Alert message")
        }
        return .none
      case .alert, .binding:
        return .none
      }
    }
    .ifLet(\.$alert, action: \.alert)
  }
}

struct ShowAlertView: View {
  @Bindable var store: StoreOf<ShowAlertFeature>
  
  var body: some View {
    VStack {
      
    }
    .alert($store.scope(state: \.alert, action: \.alert))
  }
}

Alert without custom actions

Here's how you can show a SwiftUI alert without custom action buttons using the Composable Architecture.

import ComposableArchitecture
import SwiftUI

@Reducer
struct ShowNoActionAlertFeature {
  @ObservableState
  struct State: Equatable {
    @Presents var alert: AlertState<Never>?
  }
  
  enum Action: BindableAction {
    case alert(PresentationAction<Never>)
    case binding(BindingAction<State>)
    
    case showAlert
  }

  var body: some Reducer<State, Action> {
    BindingReducer()
    Reduce { state, action in
      switch action {
      case .showAlert:
        state.alert = AlertState {
          TextState("Could not load")
        } actions: {
          // This will dismiss the alert
          ButtonState(role: .cancel) {
            TextState("OK")
          }
        } message: {
          TextState("Alert message")
        }
        return .none
      case .alert, .binding:
        return .none
      }
    }
    .ifLet(\.$alert, action: \.alert)
  }
}

struct ShowNoActionAlertView: View {
  @Bindable var store: StoreOf<ShowNoActionAlertFeature>
  
  var body: some View {
    VStack {
      Button("Show Alert") {
        store.send(.showAlert)
      }
    }
    .alert($store.scope(state: \.alert, action: \.alert))
  }
}