본문 바로가기
Swift/TCA

[TCA] day 5 Optional State를 활용하여 View조작하기(GettingStarted-OptionalState)

by 마라민초닭발로제 2024. 4. 26.

코드 전문

import ComposableArchitecture
import SwiftUI

private let readMe = """
  This screen demonstrates how to show and hide views based on the presence of some optional child \
  state.

  The parent state holds a `Counter.State?` value. When it is `nil` we will default to a plain \
  text view. But when it is non-`nil` we will show a view fragment for a counter that operates on \
  the non-optional counter state.

  Tapping "Toggle counter state" will flip between the `nil` and non-`nil` counter states.
  """

@Reducer
struct OptionalBasics {
  @ObservableState
  struct State: Equatable {
    var optionalCounter: Counter.State?
  }

  enum Action {
    case optionalCounter(Counter.Action)
    case toggleCounterButtonTapped
  }

  var body: some Reducer<State, Action> {
    Reduce { state, action in
      switch action {
      case .toggleCounterButtonTapped:
        state.optionalCounter =
          state.optionalCounter == nil
          ? Counter.State()
          : nil
        return .none
      case .optionalCounter:
        return .none
      }
    }
    .ifLet(\.optionalCounter, action: \.optionalCounter) {
      Counter()
    }
  }
}

struct OptionalBasicsView: View {
  let store: StoreOf<OptionalBasics>

  var body: some View {
    Form {
      Section {
        AboutView(readMe: readMe)
      }

      Button("Toggle counter state") {
        store.send(.toggleCounterButtonTapped)
      }

      if let store = store.scope(state: \.optionalCounter, action: \.optionalCounter) {
        Text(template: "`Counter.State` is non-`nil`")
        CounterView(store: store)
          .buttonStyle(.borderless)
          .frame(maxWidth: .infinity)
      } else {
        Text(template: "`Counter.State` is `nil`")
      }
    }
    .navigationTitle("Optional state")
  }
}

#Preview {
  NavigationStack {
    OptionalBasicsView(
      store: Store(initialState: OptionalBasics.State()) {
        OptionalBasics()
      }
    )
  }
}

#Preview("Deep-linked") {
  NavigationStack {
    OptionalBasicsView(
      store: Store(
        initialState: OptionalBasics.State(
          optionalCounter: Counter.State(
            count: 42
          )
        )
      ) {
        OptionalBasics()
      }
    )
  }
}

 

 

리드미 해석

  This screen demonstrates how to show and hide views based on the presence of some optional child   state.

  The parent state holds a `Counter.State?` value. When it is `nil` we will default to a plain  text view. But when it is non-`nil` we will show a view fragment for a counter that operates on  the non-optional counter state.

  Tapping "Toggle counter state" will flip between the `nil` and non-`nil` counter states.

이 화면은 일부 optional 하위 상태의 존재 여부에 따라 보기를 표시하고 숨기는 방법을 보여줍니다.

부모 상태는 `Counter.State?` 값을 보유합니다. 이 값이 `nil`이면 기본적으로 일반 텍스트 보기가 표시됩니다. 그러나 `nil`이 아닌 경우 옵션이 아닌 카운터 상태에서 작동하는 카운터에 대한 뷰 조각을 표시합니다.

"카운터 상태 전환"을 탭하면 `nil`과 `nil`이 아닌 카운터 상태 간에 전환됩니다.

 

 

 

Reducer{ ... }.ifLet(\.State, \.Action)

Reducer뒤에서 iflet을 활용하 state와 Action이 Nil이 아닐경우에 Store을 생성하게 한다. Action은 Enum이기에 State의 특정 Property 가 nil이 아닐 경우 Store을 생성한다. 버튼을 누를 경우 Store에서 Counter의 State를 생성하게 된다.

 

// Reducer
  var body: some Reducer<State, Action> {
    Reduce { state, action in
      switch action {
      case .toggleCounterButtonTapped:
        state.optionalCounter =
          state.optionalCounter == nil
          ? Counter.State() // Counter의 State을 생성한다.
          : nil
        return .none
      case .optionalCounter:
        return .none
      }
    }
    .ifLet(\.optionalCounter, action: \.optionalCounter) { // 만약 State가 존재할 겨우 Store 생성
      Counter()
    }
  }
}

// View
  Button("Toggle counter state") {
        store.send(.toggleCounterButtonTapped)
      }

 

 

 

 

 

 

 

 

 

게시물은 TCA 라이브러리의 CaseStudies를 직접 작성해보고 어떻게 작동하는지에 대해서 공부하기 위해서 작성했습니다.