
지난 시간에는 TCA Case Studies의 또 다른 사례를 통해 Composable Architecture를 학습했었죠. 이번 시간에는 양방향 바인딩(Bidirectional Binding)을 활용하면서, 상태를 초기화할 수 있는 리셋 기능을 추가한 예제를 소개하겠습니다.
TCA와 SwiftUI를 함께 사용하여 상태와 UI 간 연결을 매끄럽게 처리하는 방법을 살펴봅시다! 🚀
SwiftUI는 TextField, Toggle, Slider와 같은 UI 요소에서 양방향 바인딩을 제공합니다. 반면, TCA는 단방향 데이터 흐름을 기반으로 모든 상태 변경을 Reducer를 통해 관리합니다.
이 둘을 결합하려면 @BindableState와 BindingReducer를 활용해 상태와 액션을 관리해야 합니다.
아래는 리셋 버튼으로 상태를 초기화할 수 있는 기능이 추가된 Reducer 예제입니다.
@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() // @Bindable로 바인딩 처리
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
}
}
}
}
💡 주요 포인트
- State
- UI에서 사용할 sliderValue, stepCount, text, toggleIsOn 등의 상태를 정의합니다.
- Action
- binding : View와 State 간 양방향 바인딩 처리를 위한 액션입니다.
- resetButtonTapped : 리셋 버튼이 눌렸을 때 상태를 초기화하는 액션입니다.
- BindingReducer
- @Bindable을 활용해 View와 State 간 바인딩을 처리합니다.
- Reduce 로직
- stepCount 변경 시 sliderValue가 초과되지 않도록 제약을 설정합니다.
- 리셋 버튼을 누르면 상태를 초기값으로 초기화합니다.
View에서는 @Bindable을 활용해 Store와 UI 간 상태를 연결합니다. 이를 통해 양방향 바인딩을 단방향 데이터 흐름 안에서 구현합니다.
struct BindingFormView: View {
@Bindable var store: StoreOf<BindingForm>
var body: some View {
Form {
Section {
AboutView(readMe: readMe)
}
// TextField와 Toggle
HStack {
TextField("Type here", text: $store.text)
.disableAutocorrection(true)
.foregroundStyle(store.toggleIsOn ? .secondary : .primary)
Text(alternate(store.text))
}
.disabled(store.toggleIsOn)
Toggle("Disable other controls", isOn: $store.toggleIsOn.resignFirstResponder())
// Stepper와 Slider
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)
// Reset 버튼
Button("Reset") {
store.send(.resetButtonTapped)
}
.tint(.red)
}
.monospaced()
.navigationTitle("Bindings form")
}
}
💡 주요 포인트
- TextField와 Toggle
- store.text와 바인딩해 사용자의 입력값을 관리하며, Toggle 상태에 따라 UI를 비활성화합니다.
- Stepper와 Slider
- stepCount는 Slider의 최대값 역할을 하며, Stepper로 값 변경 시 Slider 값이 초과하지 않도록 조정됩니다.
- Reset 버튼
- 리셋 버튼을 누르면 .resetButtonTapped 액션이 호출되어 상태가 초기화됩니다.
Preview를 활용해 UI와 상태 변경 동작을 빠르게 테스트할 수 있습니다.
#Preview {
NavigationStack {
BindingFormView(store: .init(initialState: BindingForm.State(), reducer: {
BindingForm()
}))
}
}
이전 예제에서는 개별 액션(sliderValueChanged, textChanged 등)을 정의해 상태 변경을 처리했습니다.
이번 예제에서는 TCA의 BindableState와 BindingReducer를 활용해, 상태 변경 로직을 간결하게 작성할 수 있었습니다.
| Binding Basic 코드 | Binding Form 코드 |
|---|---|
| Action에 개별 변경 | BindingAction 으로 바인딩 처리 통함 |
| 상태 변경 로직을 세분화하여 작성 | BindingReducer 로 기본 로직 간소화 |
| 단일 상태 변경을 위한 여러 액션 관리 필요 | .binding 을 사용해 액션 관리 최소화 |
장점
- 간결성: 기존 코드보다 액션과 상태 변경 로직이 단순해졌습니다.
- 유지보수성: 새로운 UI 요소가 추가되어도 @Bindable과 BindingReducer를 활용하면 쉽게 확장할 수 있습니다.
- TCA의 양방향 바인딩
- @Bindable과 BindingReducer를 통해 상태와 UI 간 연결을 간소화했습니다.
- 리셋 기능 추가
- 리셋 버튼을 눌러 상태를 초기화하는 방법을 배웠습니다.
- 상태 관리 효율성
- 기존 방식보다 간결하고 효율적으로 상태를 관리할 수 있었습니다.
이번 포스팅을 통해 TCA와 SwiftUI를 결합해 효율적인 상태 관리를 구현하는 방법을 익히셨길 바랍니다.
다음에도 더 흥미롭고 유익한 내용으로 찾아뵙겠습니다! 😊
감사합니다!