본문 바로가기
Swift/TCA

[TCA] day 4 BindableAction Portocol을 활용하여 Binding 하기(GettingStarted-Bindings-Forms)

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

코드 전문

import ComposableArchitecture
import SwiftUI

private let readMe = """
  This file demonstrates how to handle two-way bindings in the Composable Architecture using \
  bindable actions and binding reducers.

  Bindable actions allow you to safely eliminate the boilerplate caused by needing to have a \
  unique action for every UI control. Instead, all UI bindings can be consolidated into a single \
  `binding` action, which the `BindingReducer` can automatically apply to state.

  It is instructive to compare this case study to the "Binding Basics" case study.
  """

@Reducer
struct BindingForm {
  @ObservableState
  struct State: Equatable {
    var sliderValue = 5.0
    var stepCount = 10
    var text = ""
    var toggleIsOn = false
  }

  enum Action: BindableAction {
    case binding(BindingAction<State>)
    case resetButtonTapped
  }

  var body: some Reducer<State, Action> {
    BindingReducer()
    Reduce { state, action in
      switch action {
      case .binding(\.stepCount):
        state.sliderValue = .minimum(state.sliderValue, Double(state.stepCount))
        return .none

      case .binding:
        return .none

      case .resetButtonTapped:
        state = State()
        return .none
      }
    }
  }
}

struct BindingFormView: View {
  @Bindable var store: StoreOf<BindingForm>

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

      HStack {
        TextField("Type here", text: $store.text)
          .disableAutocorrection(true)
          .foregroundStyle(store.toggleIsOn ? Color.secondary : .primary)
        Text(alternate(store.text))
      }
      .disabled(store.toggleIsOn)

      Toggle("Disable other controls", isOn: $store.toggleIsOn.resignFirstResponder())

      Stepper(
        "Max slider value: \(store.stepCount)",
        value: $store.stepCount,
        in: 0...100
      )
      .disabled(store.toggleIsOn)

      HStack {
        Text("Slider value: \(Int(store.sliderValue))")

        Slider(value: $store.sliderValue, in: 0...Double(store.stepCount))
          .tint(.accentColor)
      }
      .disabled(store.toggleIsOn)

      Button("Reset") {
        store.send(.resetButtonTapped)
      }
      .tint(.red)
    }
    .monospacedDigit()
    .navigationTitle("Bindings form")
  }
}

private func alternate(_ string: String) -> String {
  string
    .enumerated()
    .map { idx, char in
      idx.isMultiple(of: 2)
        ? char.uppercased()
        : char.lowercased()
    }
    .joined()
}

#Preview {
  NavigationStack {
    BindingFormView(
      store: Store(initialState: BindingForm.State()) {
        BindingForm()
      }
    )
  }
}

 

 

리드미 해석

 Bindable actions allow you to safely eliminate the boilerplate caused by needing to have a  unique action for every UI control. Instead, all UI bindings can be consolidated into a single  `binding` action, which the `BindingReducer` can automatically apply to state.
  It is instructive to compare this case study to the "Binding Basics" case study.

바인딩 가능한 액션을 사용하면 모든 UI 컨트롤에 고유한 액션이 필요하기 때문에 발생하는 보일러 플레이트를 안전하게 제거할 수 있습니다. 대신 모든 UI 바인딩을 하나의 '바인딩' 액션으로 통합할 수 있으며, '바인딩 리듀서'가 자동으로 state에 적용할 수 있습니다.
이 사례 연구를 "바인딩 기초" 사례 연구와 비교하면 도움이 될 것입니다.
deepl

 

 

 

 

Binding Property

이전에 우리는 단방향 Reducer을 통해서 state가 일정한 단방향으로 변하는 것을 보았습니다. $path.<State>.sending(\.<Action>)을 통해서 전달했습니다. 하지만 다른 방법도 있습니다. 이는 bindingAction이라는 Action에서 enum을 만들어 쓸 수 있습니다. 이렇게 쓴다면 기존 depth가 깊은 코드보다 짧게 작성이 가능합니다. ($store.<State>)

 

BindingAction을 활용하기 위해서는

- Action BindalbeAction을 추가하기

- reducer에 BindingReducer()함수 부르기  

  struct State: Equatable {
    var sliderValue = 5.0
    var stepCount = 10
    var text = ""
    var toggleIsOn = false
  }

  enum Action: BindableAction {
    case binding(BindingAction<State>)
    case resetButtonTapped
  }

  var body: some Reducer<State, Action> {
      BindingReducer()

// BidingAction<State>을 적용했을 때
Toggle(
	"Disable other controls",
    isOn: $store.toggleIsOn.resignFirstResponder()
)


// BidingAction<State>을 적용하지 않았을 때
Toggle(
    "Disable other controls",
    isOn: $store.toggleIsOn.sending(\.toggleChanged).resignFirstResponder()
)

 

 

 

 

 

 

 

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