Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
Tags
- 프로그래머스
- 자바
- 토이프로젝트
- 리뷰
- 자바공부
- 코딩공부
- IOS
- 티스토리챌린지
- java
- SOPT
- 영화
- Flutter Toy Project
- 독서일기
- sopt ios
- 영화기록
- SWIFT
- 오블완
- 새벽녘 소소한 기록
- swift concurrency
- 자바 스터디
- Flutter
- 플러터
- 영화후기
- 키노
- 영화리뷰
- MVVM-C
- 영화일기
- 일기
- 백준
- sopt 35기
Archives
- Today
- Total
새벽의 기록
[iOS] TCA Zero-to-Hero #3 - Binding: TextField, Toggle 등 UI 바인딩 처리 본문
목표: 카운터 앱에 "알림 켜기/끄기(Toggle)" 스위치와 "메모 입력(TextField)" 기능을 추가.
import SwiftUI
import ComposableArchitecture
@Reducer
struct CounterFeature {
@ObservableState
struct State: Equatable {
var count = 0
var isLoading = false
var isTimerEnabled = false
var memo = ""
}
enum Action: BindableAction {
case incrementButtonTapped
case decrementButtonTapped
case delayedIncrementButtonTapped
case incrementResponse
case binding(BindingAction<State>)
}
var body: some Reducer<State, Action> {
BindingReducer()
Reduce { state, action in
switch action {
case .incrementButtonTapped:
state.count += 1
return .none
case .decrementButtonTapped:
state.count -= 1
return .none
case .delayedIncrementButtonTapped:
state.isLoading = true // 로딩 시작
return .run { send in
// 복잡한 비동기 로직(API 호출, 타이머 등)을 수행.
try await Task.sleep(nanoseconds: 1_000_000_000) // 1초 대기
// 작업이 끝나면 다시 Action을 날려서 State 변경.
await send(.incrementResponse)
}
case .incrementResponse:
state.isLoading = false // 로딩 끝
state.count += 1
return .none
// "들어온 binding 액션이 정확히 'isTimerEnabled' 변수를 건드린 경우라면 이쪽으로 와라"
case .binding(\.isTimerEnabled):
print("타이머 스위치가 변경되었습니다: \(state.isTimerEnabled)")
return .none
// "위에서 걸러지지 않은 나머지 모든 binding 액션은 여기서 처리해라"
case .binding:
return .none
}
}
}
}
struct CounterView: View {
@Bindable var store: StoreOf<CounterFeature>
var body: some View {
VStack {
if store.isLoading {
ProgressView().padding()
} else {
Text("\(store.count)")
.font(.largeTitle)
.padding()
}
HStack {
Button("-") { store.send(.decrementButtonTapped) }
.font(.largeTitle)
.padding()
.background(Color.black.opacity(0.1))
.cornerRadius(10)
Button("+") { store.send(.incrementButtonTapped) }
.font(.largeTitle)
.padding()
.background(Color.black.opacity(0.1))
.cornerRadius(10)
Button("1초 뒤") { store.send(.delayedIncrementButtonTapped) }
.font(.headline)
.padding()
.background(Color.blue.opacity(0.2))
.cornerRadius(10)
}
.padding()
Divider().padding()
Toggle("타이머 활성화", isOn: $store.isTimerEnabled)
.padding()
.background(store.isTimerEnabled ? Color.green.opacity(0.2) : Color.gray.opacity(0.1))
.cornerRadius(8)
TextField("메모를 입력하세요", text: $store.memo)
.textFieldStyle(.roundedBorder)
.padding(.top)
Text("입력 중: \(store.memo)")
.font(.caption)
.foregroundStyle(.gray)
}
.padding()
}
}
#Preview {
CounterView(
store: Store(initialState: CounterFeature.State()) {
CounterFeature()
._printChanges()
}
)
}

'[iOS]' 카테고리의 다른 글
| [iOS] TCA Zero-to-Hero #4 - Dependencies: 의존성 주입과 통제 (0) | 2025.12.26 |
|---|---|
| [iOS] TCA Zero-to-Hero #2 - Effect: 비동기 처리와 외부 통신 (0) | 2025.12.21 |
| [iOS] TCA Zero-to-Hero #1 - State, Action, Reducer의 단방향 데이터 흐름 이해 (0) | 2025.12.20 |
| [iOS] Swift Concurrency 박살내기 #4 - Sendable과 nonisolated (0) | 2025.12.06 |
| [iOS] 결합도와 응집도에 관해 + CoordinatorView에 대한 스스로의 판단 한 스푼 (1) | 2025.11.26 |
Comments
