SwiftUI | TCA | |
์ฅ์ | - State ์ ๋ณ๊ฒฝ -> UI์ ์ฆ์ ๋ฐ์ - ์ฝ๋ ์์ฑ์ด ๊ฐ๋จํจ |
- State ๋ณ๊ฒฝ ๋ก์ง ๊ด๋ฆฌ ์ฉ์ด - ๋ณต์กํ State์ ์ด์ ๋ฐ๋ฅธ Side Effect ์ฒ๋ฆฌ ์ฉ์ด |
๋จ์ | - State ๊ด๋ฆฌ๊ฐ ๋ณต์กํด ์ง์๋ก State ๋ณํ์ ๋ฐ๋ฅธ side effect๋ฅผ ๊ด๋ฆฌํ๊ธฐ ์ด๋ ค์ | - ์ด๋ ค์ด ๊ตฌํ ๋์ด๋ ๐ฅ ๐ซ |
TCA Binding
public func binding<Value>(
get: @escaping (_ state: ViewState) -> Value,
send valueToAction: @escaping (_ value: Value) -> ViewAction
) -> Binding<Value> {
ObservedObject(wrappedValue: self)
.projectedValue[get: .init(rawValue: get), send: .init(rawValue: valueToAction)]
}
- get : State๋ฅผ ๋ฐ์ธ๋ฉ์ ๊ฐ์ผ๋ก ๋ณํํ๋๋ก ํ๋ ํด๋ก์
- send : ๋ฐ์ธ๋ฉ์ ๊ฐ์ ๋ค์ Store์ ํผ๋๋ฐฑ ํ๋ Action์ผ๋ก ๋ณํํ๋ ํด๋ก์
SwiftUI์์ ChildView์๊ฒ Binding<Value>๋ฅผ ์ ๋ฌํ ๋๊ฐ ์๋๋ฐ ์ binding<Value>๋ ๊ทธ ๋ ์ฌ์ฉํ๋ ๊ฒ
Text("์ฟ ํฐ")
.padding(.leading, 10)
VStack {
TextField("ํ๋ก๋ชจ์
์ฝ๋๋ฅผ ์
๋ ฅํด ์ฃผ์ธ์.", text: .constant(""))
}
.padding()
.border(.black, width: 1)
.padding(.horizontal, 10)
(text์ Binding<String>์ผ๋ก ์์ State๋ฅผ ์ ๋ฌํ๊ฒ ๋๋๋ฐ ์ด๋์ State๋ ์ด๋ป๊ฒ ๊ด๋ฆฌํ ๊ฒ์ธ๊ฐ์ ๊ดํ๊ฒ)
binding(get:send:) ์ฌ์ฉ ํ๋ ๋ฒ
struct State: Equatable {
var couponCode = ""
// ์๋ต
}
State์ ํ ์คํธํ๋์ ๋ฐ์ธ๋ฉ ํ ํ๋กํผํฐ๋ฅผ ์ ์
enum Action: Equatable {
case couponCodeInserted(String)
}
View์์ ํ ์คํธ ํ๋๋ฅผ ํตํด ํ ์คํธ๋ฅผ ์ ๋ ฅํ๋ฉด Store๊ฐ ์๋ Store ์ธ๋ถ์ ์๋ TextField ๋ด๋ถ์์ State๋ฅผ ์กฐ์ ํ๊ฒ ๋จ
์ด๋์ Action์ ์ ์
Reduce { state, action in
switch action {
case .couponCodeInserted(let code):
state.couponCode = code
return .none
}
}
reducer์์ Action์ ๋ํ ๊ฒฐ๊ณผ๋ก State์ ๊ฐ์ ๋ณ๊ฒฝ
VStack {
TextField("ํ๋ก๋ชจ์
์ฝ๋๋ฅผ ์
๋ ฅํด ์ฃผ์ธ์.",
text: viewStore.binding(
get: \.couponCode,
send: { .couponCodeInserted($0) }
))}
.padding()
.border(.black, width: 1)
.padding(.horizontal, 10)
- TextField์ Binding<String>์ผ๋ก ๋๊ฒจ์ค๋ค. ์ด๋ Store์ binding(get:send:)๋ฅผ ์ฌ์ฉ
- viewStore.binding(get:send:)์์ get์ ์ ๋ฌ๋ ๊ฐ์ฒด ๋ฐ์ธ๋ฉ ํ์ฌ text์ ์ ๋ฌ, send์ ์ ๋ฌ๋ Action์ Store์ ๋ค์ ํผ๋๋ฐฑ, ์ดํ ํด๋น Action์ ํด๋นํ๋ reducer ๋ก์ง์ด ์คํ
- get์ผ๋ก State, send๋ก Action
- ์ด๋ฒคํธ๋ฅผ ๋จ๋ฐฉํฅ ํต์ ์ผ๋ก ๋ฐ๊พธ๊ณ State์ ์ฌ์ด๋ ์ดํํธ๋ฅผ ๊ด๋ฆฌํ ์ ์๊ฒ ๋จ
binding(get:send)์ ๋จ์
- ๊ฐ ์๋ธ ๋ทฐ์์ State๋ฅผ ๊ด๋ฆฌํ๋ ๋ฐฉ์์ด ์๋๊ธฐ ๋๋ฌธ์ ๋น์ฆ๋์ค ๋ก์ง์ด ๋ ํฌ๊ณ ๋ณต์กํด ์ง ์๋ก ๋ง์ ๋ฐ์ธ๋ฉ ์ฝ๋๊ฐ ์์ฑ๋๋ฉฐ reducer์ ๊ด๋ฆฌ๊ฐ ๋ณต์กํด ์ง.
- ๊ฐ๋ ์ฑ์ด ๋๋น ์ง๊ณ ๋ฐ๋ณต๋๋ ์ฝ๋ ์์ ์ด ํ์ํ๊ฒ ๋จ
@BindingState
- @BindingState๋ฅผ ์ฌ์ฉํ๋ฉด SwiftUI์ UI ์ปจํธ๋กค์ ๋ฐ์ธ๋ฉ ๊ฐ๋ฅ
- View์์ ํด๋น ํ๋ ๊ฐ ์กฐ์ ๊ฐ๋ฅ
- View์์ ํด๋น ํ๋ ๊ฐ์ ๋ณ๊ฒฝํ ์ ์๋๋ก ํ๋ ค๋ฉด @BindingState๋ฅผ ๋ถ์ด์ง ์์์ผ ํจ
- SwiftUI์์ Binding์ State๋ฅผ ๋ค๋ฅธ ๋ทฐ์ ๊ณต์ ํ๋ ๊ฐ๋ ์ด๊ธฐ ๋๋ฌธ์ Source of Truth ๊ด๋ฆฌ๊ฐ ์ด๋ ค์์ง๋ค. ๋ฐ๋ผ์ ๋ชจ๋ ํ๋์ @BindingState๋ฅผ ์ฌ์ฉํ๋๊ฑด ๊ถ์ฅ๋์ง ์์ (์บก์ํ ์์)
- ์ปดํฌ๋ํธ์ ์ ๋ฌํ๊ธฐ ์ํ ํ๋์๋ง ์ฌ์ฉ
struct State: Equatable {
@BindingState var isToggled = false
}
BindableAction protocol
enum Action: BindableAction, Equatable {
case binding(BindingAction<State>)
}
- Action enum์ BindableAction ํ๋กํ ์ฝ ์ฑํ
- BindingState๋ฅผ ์ฌ์ฉํ ํ๋๋ ์ด ํ๋์ Action์ ์ฐ๊ฒฐ๋จ
struct BindingAction<Root>: CasePathable, Equatable, @unchecked Sendable
- ํ๋์ Action case๊ฐ ์ฌ๋ฌ BindingState์ ๋์ํ ์ ์๊ฒ ํ๊ธฐ ์ํด ์ ๋ค๋ฆญ ํ์ ์ ๊ฐ์ง
- ์ ๋ค๋ฆญ ํ์ ์ reducer์ State
associatedtype State
- bindable ๋ด์๋ State ํ์ ์ ์ ๋ค๋ฆญ์ผ๋ก ๋ฐ๊ธฐ ์ํด associatedtype์ผ๋ก State๊ฐ ์ ์๋์ด ์์(๋ฐ์ธ๋ฉ์ ์ํ ์ํ ๊ฐ์ ํ์ ์ ์ฌ๋ฌ๊ฐ์ง๊ฐ ์ฌ ์ ์์)
BindingReducer
var body: some ReducerOf<Self> {
BindingReducer()
- reducer์๋ BindingReducer๋ฅผ ์ฌ์ฉํด์ State ๋ณ๊ฒฝ์ ๊ฐ๋จํ๊ฒ ํ ์ ์์
- Binding action์ด ์์ ๋๋ฉด State๋ฅผ ์ ๋ฐ์ดํธ ํด์ฃผ๋ reducer
- Store ๋ด๋ถ ๋ฐ์ธ๋ฉ ๊ฐ๋ฅํ State(์์ ์ฝ๋์์๋isToggled) ํ๋๊ฐ ์ ๋ฐ์ดํธ ๋๋ฉด BindingReducer()๊ฐ ์ ๋ฐ์ดํธ ๋ ํ๋ ๊ฐ๊ณผ ํจ๊ป Action ์์ ํ Reducer ํด๋ก์ ๋ด์ ๋๋ฉ์ธ ๋ก์ง ์ฒ๋ฆฌํ์ฌ State์ ๊ฒฐ๊ณผ ๋ฐ์
- State์ Action ์ฌ์ด๋ฅผ ๋ฐ์ธ๋ฉ ํ๋ ์ญํ
Reduce { state, action in
- action์ด BindableAction์ ๋ฐ๋ฅด๊ฒ ๋จ
static func binding(_ action: BindingAction<State>) -> Self
- BindableAction์ ์ ๋ฉ์๋๋ BindingAction์ ๋ฐํ
- BindingState๋ก ์ ์ํ State๋ฅผ ์ง์ ํด์ฃผ๋ฉด ๋จ
case .binding(\.$isToggled):
return .none
case .binding(_):
return .none
- Effect๋ฅผ ๋ฐํํ๋ Reduce ํด๋ก์ ์๋ case .bindg(_) ์์ ์ ์ํด ์ฃผ๋ฉฐ ํคํจ์ค๋ฅผ ์ฌ์ฉํด์ ์ ๊ทผ
- .binding(_) ๋ ์ ์ด์ฃผ์ด์ผ ํจ
BindingState ํ๋กํผํฐ ๋ํผ์ ์ฌ์ฉ
Toggle("test ์
๋๋ค.", isOn: viewStore.$isToggled)
._printChanges()
received action:
ProductDetailFeature.Action.amountControl(.plusButtonTapped)
ProductDetailFeature.State(
- productAmount: 1,
+ productAmount: 2,
- amountControl: AmountControl.State(amount: 1)
+ amountControl: AmountControl.State(amount: 2)
_isToggled: true
)
- ๋ฐ์ธ๋ฉ ํ๋กํผํฐ๋ ์ธ๋๋ฐ๋ก ํ์๋จ
View State Binding
- Store ์ธ๋ถ์ ์๋ View State๋ฅผ ๋ฐ์ธ๋ฉํ๊ธฐ ์ํด์ BindingViewState ํ๋กํผํฐ ๋ํผ๋ฅผ ์ฌ์ฉ
struct CartButton: View { }
- Child View ํน์ Sub View๋ฅผ ๋ง๋ค๋ฉด Binding์ ํตํด์ ๊ฐ์ ๊ณต์ ํด์ผ ํ๋ ์ํฉ์ด ๋ฐ์ํ๊ฒ ๋๋๋ฐ ์ด๋ BindingViewState๋ฅผ ์ฌ์ฉํ ์ ์์
let store: StoreOf<ProductDetailFeature>
- ํ์๋ทฐ์ ์์ ์คํ ์ด๋ฅผ ์ ์ํด ์ฃผ๊ณ init ์์ ์ ์ ๋ฌ ๋ฐ์
struct ViewState: Equatable {
@BindingViewState var productAmount: Int
}
- ViewState๋ฅผ ๊ตฌ์กฐ์ฒด๋ฅผ ๋ง๋ค๊ณ ๋ฐ์ธ๋ฉ์ด ํ์ํ ๊ฐ์ ์ ์ ํด์ค
var body: some View {
WithViewStore(store, observe: { bindingViewStore in
ViewState(productAmount: bindingViewStore.$productAmount)
}) { viewStore in
ZStack(alignment: .bottomLeading) {
Image(systemName: "cart")
.foregroundStyle(.green)
.font(.system(size: 30))
if viewStore.productAmount > 0 {
Circle()
.frame(width: 20, height: 20)
.foregroundStyle(.red)
.overlay(
Text("\(viewStore.productAmount)")
.foregroundStyle(.white)
.fontWeight(.heavy)
)
.offset(x: -5, y: 8)
}
}
.frame(width: 50, height: 50)
}
}
- ์์ State์์ ์ ๋ฌ ๋ฐ์ ๊ฐ ์ค์ ํ์ ๋ทฐ์์๋ง ์ฌ์ฉํ ๊ฐ์ ์ง์ ํ๊ธฐ ์ํด์ WithViewStore์ observe ํ๋ผ๋ฏธํฐ๋ฅผ ์ง์ ํด ์ค
- BindingViewStore์ ์ฌ์ฉํด์ ์์ ๋ทฐ์ ๋ชจ๋ ๊ฐ์ ์ ๊ทผํ๋ ๊ฒ์ด ์๋ ํ์ํ ๊ฐ๋ง ๊ณต์ ํ ์ ์์
- ViewState์์ BindingViewState๋ก ์ง์ ํ ๊ฐ๊ณผ ์์ ViewStore๋ก ๋ถํฐ ์ ๋ฌ ๋ฐ์ State๋ฅผ ๋ฐ์ธ๋ฉ ํด์ค
CartButton(store: self.store)
- ์์๋ทฐ์์ ํ์๋ทฐ๋ฅผ ์ฌ์ฉํ ๋๋ store๋ฅผ ์ ๋ฌํด ์ฃผ๋ฉด ๋๋ค.

'iOS ๐ > Architecture' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[TCA] TCA์์์ ๋น๋๊ธฐ ์ฒ๋ฆฌ (0) | 2023.12.21 |
---|---|
[TCA] Overriding Dependencies (feat. Dependency Injection) (0) | 2023.12.13 |
[SwiftUI/TCA] Scope (1) | 2023.11.30 |
[SwiftUI/TCA] Store and ViewStore (0) | 2023.11.29 |
[SwiftUI/TCA] Effect ๊ตฌํ๊ณผ ํ์ฉ (0) | 2023.11.29 |
SwiftUI | TCA | |
์ฅ์ | - State ์ ๋ณ๊ฒฝ -> UI์ ์ฆ์ ๋ฐ์ - ์ฝ๋ ์์ฑ์ด ๊ฐ๋จํจ |
- State ๋ณ๊ฒฝ ๋ก์ง ๊ด๋ฆฌ ์ฉ์ด - ๋ณต์กํ State์ ์ด์ ๋ฐ๋ฅธ Side Effect ์ฒ๋ฆฌ ์ฉ์ด |
๋จ์ | - State ๊ด๋ฆฌ๊ฐ ๋ณต์กํด ์ง์๋ก State ๋ณํ์ ๋ฐ๋ฅธ side effect๋ฅผ ๊ด๋ฆฌํ๊ธฐ ์ด๋ ค์ | - ์ด๋ ค์ด ๊ตฌํ ๋์ด๋ ๐ฅ ๐ซ |
TCA Binding
public func binding<Value>(
get: @escaping (_ state: ViewState) -> Value,
send valueToAction: @escaping (_ value: Value) -> ViewAction
) -> Binding<Value> {
ObservedObject(wrappedValue: self)
.projectedValue[get: .init(rawValue: get), send: .init(rawValue: valueToAction)]
}
- get : State๋ฅผ ๋ฐ์ธ๋ฉ์ ๊ฐ์ผ๋ก ๋ณํํ๋๋ก ํ๋ ํด๋ก์
- send : ๋ฐ์ธ๋ฉ์ ๊ฐ์ ๋ค์ Store์ ํผ๋๋ฐฑ ํ๋ Action์ผ๋ก ๋ณํํ๋ ํด๋ก์
SwiftUI์์ ChildView์๊ฒ Binding<Value>๋ฅผ ์ ๋ฌํ ๋๊ฐ ์๋๋ฐ ์ binding<Value>๋ ๊ทธ ๋ ์ฌ์ฉํ๋ ๊ฒ
Text("์ฟ ํฐ")
.padding(.leading, 10)
VStack {
TextField("ํ๋ก๋ชจ์
์ฝ๋๋ฅผ ์
๋ ฅํด ์ฃผ์ธ์.", text: .constant(""))
}
.padding()
.border(.black, width: 1)
.padding(.horizontal, 10)
(text์ Binding<String>์ผ๋ก ์์ State๋ฅผ ์ ๋ฌํ๊ฒ ๋๋๋ฐ ์ด๋์ State๋ ์ด๋ป๊ฒ ๊ด๋ฆฌํ ๊ฒ์ธ๊ฐ์ ๊ดํ๊ฒ)
binding(get:send:) ์ฌ์ฉ ํ๋ ๋ฒ
struct State: Equatable {
var couponCode = ""
// ์๋ต
}
State์ ํ ์คํธํ๋์ ๋ฐ์ธ๋ฉ ํ ํ๋กํผํฐ๋ฅผ ์ ์
enum Action: Equatable {
case couponCodeInserted(String)
}
View์์ ํ ์คํธ ํ๋๋ฅผ ํตํด ํ ์คํธ๋ฅผ ์ ๋ ฅํ๋ฉด Store๊ฐ ์๋ Store ์ธ๋ถ์ ์๋ TextField ๋ด๋ถ์์ State๋ฅผ ์กฐ์ ํ๊ฒ ๋จ
์ด๋์ Action์ ์ ์
Reduce { state, action in
switch action {
case .couponCodeInserted(let code):
state.couponCode = code
return .none
}
}
reducer์์ Action์ ๋ํ ๊ฒฐ๊ณผ๋ก State์ ๊ฐ์ ๋ณ๊ฒฝ
VStack {
TextField("ํ๋ก๋ชจ์
์ฝ๋๋ฅผ ์
๋ ฅํด ์ฃผ์ธ์.",
text: viewStore.binding(
get: \.couponCode,
send: { .couponCodeInserted($0) }
))}
.padding()
.border(.black, width: 1)
.padding(.horizontal, 10)
- TextField์ Binding<String>์ผ๋ก ๋๊ฒจ์ค๋ค. ์ด๋ Store์ binding(get:send:)๋ฅผ ์ฌ์ฉ
- viewStore.binding(get:send:)์์ get์ ์ ๋ฌ๋ ๊ฐ์ฒด ๋ฐ์ธ๋ฉ ํ์ฌ text์ ์ ๋ฌ, send์ ์ ๋ฌ๋ Action์ Store์ ๋ค์ ํผ๋๋ฐฑ, ์ดํ ํด๋น Action์ ํด๋นํ๋ reducer ๋ก์ง์ด ์คํ
- get์ผ๋ก State, send๋ก Action
- ์ด๋ฒคํธ๋ฅผ ๋จ๋ฐฉํฅ ํต์ ์ผ๋ก ๋ฐ๊พธ๊ณ State์ ์ฌ์ด๋ ์ดํํธ๋ฅผ ๊ด๋ฆฌํ ์ ์๊ฒ ๋จ
binding(get:send)์ ๋จ์
- ๊ฐ ์๋ธ ๋ทฐ์์ State๋ฅผ ๊ด๋ฆฌํ๋ ๋ฐฉ์์ด ์๋๊ธฐ ๋๋ฌธ์ ๋น์ฆ๋์ค ๋ก์ง์ด ๋ ํฌ๊ณ ๋ณต์กํด ์ง ์๋ก ๋ง์ ๋ฐ์ธ๋ฉ ์ฝ๋๊ฐ ์์ฑ๋๋ฉฐ reducer์ ๊ด๋ฆฌ๊ฐ ๋ณต์กํด ์ง.
- ๊ฐ๋ ์ฑ์ด ๋๋น ์ง๊ณ ๋ฐ๋ณต๋๋ ์ฝ๋ ์์ ์ด ํ์ํ๊ฒ ๋จ
@BindingState
- @BindingState๋ฅผ ์ฌ์ฉํ๋ฉด SwiftUI์ UI ์ปจํธ๋กค์ ๋ฐ์ธ๋ฉ ๊ฐ๋ฅ
- View์์ ํด๋น ํ๋ ๊ฐ ์กฐ์ ๊ฐ๋ฅ
- View์์ ํด๋น ํ๋ ๊ฐ์ ๋ณ๊ฒฝํ ์ ์๋๋ก ํ๋ ค๋ฉด @BindingState๋ฅผ ๋ถ์ด์ง ์์์ผ ํจ
- SwiftUI์์ Binding์ State๋ฅผ ๋ค๋ฅธ ๋ทฐ์ ๊ณต์ ํ๋ ๊ฐ๋ ์ด๊ธฐ ๋๋ฌธ์ Source of Truth ๊ด๋ฆฌ๊ฐ ์ด๋ ค์์ง๋ค. ๋ฐ๋ผ์ ๋ชจ๋ ํ๋์ @BindingState๋ฅผ ์ฌ์ฉํ๋๊ฑด ๊ถ์ฅ๋์ง ์์ (์บก์ํ ์์)
- ์ปดํฌ๋ํธ์ ์ ๋ฌํ๊ธฐ ์ํ ํ๋์๋ง ์ฌ์ฉ
struct State: Equatable {
@BindingState var isToggled = false
}
BindableAction protocol
enum Action: BindableAction, Equatable {
case binding(BindingAction<State>)
}
- Action enum์ BindableAction ํ๋กํ ์ฝ ์ฑํ
- BindingState๋ฅผ ์ฌ์ฉํ ํ๋๋ ์ด ํ๋์ Action์ ์ฐ๊ฒฐ๋จ
struct BindingAction<Root>: CasePathable, Equatable, @unchecked Sendable
- ํ๋์ Action case๊ฐ ์ฌ๋ฌ BindingState์ ๋์ํ ์ ์๊ฒ ํ๊ธฐ ์ํด ์ ๋ค๋ฆญ ํ์ ์ ๊ฐ์ง
- ์ ๋ค๋ฆญ ํ์ ์ reducer์ State
associatedtype State
- bindable ๋ด์๋ State ํ์ ์ ์ ๋ค๋ฆญ์ผ๋ก ๋ฐ๊ธฐ ์ํด associatedtype์ผ๋ก State๊ฐ ์ ์๋์ด ์์(๋ฐ์ธ๋ฉ์ ์ํ ์ํ ๊ฐ์ ํ์ ์ ์ฌ๋ฌ๊ฐ์ง๊ฐ ์ฌ ์ ์์)
BindingReducer
var body: some ReducerOf<Self> {
BindingReducer()
- reducer์๋ BindingReducer๋ฅผ ์ฌ์ฉํด์ State ๋ณ๊ฒฝ์ ๊ฐ๋จํ๊ฒ ํ ์ ์์
- Binding action์ด ์์ ๋๋ฉด State๋ฅผ ์ ๋ฐ์ดํธ ํด์ฃผ๋ reducer
- Store ๋ด๋ถ ๋ฐ์ธ๋ฉ ๊ฐ๋ฅํ State(์์ ์ฝ๋์์๋isToggled) ํ๋๊ฐ ์ ๋ฐ์ดํธ ๋๋ฉด BindingReducer()๊ฐ ์ ๋ฐ์ดํธ ๋ ํ๋ ๊ฐ๊ณผ ํจ๊ป Action ์์ ํ Reducer ํด๋ก์ ๋ด์ ๋๋ฉ์ธ ๋ก์ง ์ฒ๋ฆฌํ์ฌ State์ ๊ฒฐ๊ณผ ๋ฐ์
- State์ Action ์ฌ์ด๋ฅผ ๋ฐ์ธ๋ฉ ํ๋ ์ญํ
Reduce { state, action in
- action์ด BindableAction์ ๋ฐ๋ฅด๊ฒ ๋จ
static func binding(_ action: BindingAction<State>) -> Self
- BindableAction์ ์ ๋ฉ์๋๋ BindingAction์ ๋ฐํ
- BindingState๋ก ์ ์ํ State๋ฅผ ์ง์ ํด์ฃผ๋ฉด ๋จ
case .binding(\.$isToggled):
return .none
case .binding(_):
return .none
- Effect๋ฅผ ๋ฐํํ๋ Reduce ํด๋ก์ ์๋ case .bindg(_) ์์ ์ ์ํด ์ฃผ๋ฉฐ ํคํจ์ค๋ฅผ ์ฌ์ฉํด์ ์ ๊ทผ
- .binding(_) ๋ ์ ์ด์ฃผ์ด์ผ ํจ
BindingState ํ๋กํผํฐ ๋ํผ์ ์ฌ์ฉ
Toggle("test ์
๋๋ค.", isOn: viewStore.$isToggled)
._printChanges()
received action:
ProductDetailFeature.Action.amountControl(.plusButtonTapped)
ProductDetailFeature.State(
- productAmount: 1,
+ productAmount: 2,
- amountControl: AmountControl.State(amount: 1)
+ amountControl: AmountControl.State(amount: 2)
_isToggled: true
)
- ๋ฐ์ธ๋ฉ ํ๋กํผํฐ๋ ์ธ๋๋ฐ๋ก ํ์๋จ
View State Binding
- Store ์ธ๋ถ์ ์๋ View State๋ฅผ ๋ฐ์ธ๋ฉํ๊ธฐ ์ํด์ BindingViewState ํ๋กํผํฐ ๋ํผ๋ฅผ ์ฌ์ฉ
struct CartButton: View { }
- Child View ํน์ Sub View๋ฅผ ๋ง๋ค๋ฉด Binding์ ํตํด์ ๊ฐ์ ๊ณต์ ํด์ผ ํ๋ ์ํฉ์ด ๋ฐ์ํ๊ฒ ๋๋๋ฐ ์ด๋ BindingViewState๋ฅผ ์ฌ์ฉํ ์ ์์
let store: StoreOf<ProductDetailFeature>
- ํ์๋ทฐ์ ์์ ์คํ ์ด๋ฅผ ์ ์ํด ์ฃผ๊ณ init ์์ ์ ์ ๋ฌ ๋ฐ์
struct ViewState: Equatable {
@BindingViewState var productAmount: Int
}
- ViewState๋ฅผ ๊ตฌ์กฐ์ฒด๋ฅผ ๋ง๋ค๊ณ ๋ฐ์ธ๋ฉ์ด ํ์ํ ๊ฐ์ ์ ์ ํด์ค
var body: some View {
WithViewStore(store, observe: { bindingViewStore in
ViewState(productAmount: bindingViewStore.$productAmount)
}) { viewStore in
ZStack(alignment: .bottomLeading) {
Image(systemName: "cart")
.foregroundStyle(.green)
.font(.system(size: 30))
if viewStore.productAmount > 0 {
Circle()
.frame(width: 20, height: 20)
.foregroundStyle(.red)
.overlay(
Text("\(viewStore.productAmount)")
.foregroundStyle(.white)
.fontWeight(.heavy)
)
.offset(x: -5, y: 8)
}
}
.frame(width: 50, height: 50)
}
}
- ์์ State์์ ์ ๋ฌ ๋ฐ์ ๊ฐ ์ค์ ํ์ ๋ทฐ์์๋ง ์ฌ์ฉํ ๊ฐ์ ์ง์ ํ๊ธฐ ์ํด์ WithViewStore์ observe ํ๋ผ๋ฏธํฐ๋ฅผ ์ง์ ํด ์ค
- BindingViewStore์ ์ฌ์ฉํด์ ์์ ๋ทฐ์ ๋ชจ๋ ๊ฐ์ ์ ๊ทผํ๋ ๊ฒ์ด ์๋ ํ์ํ ๊ฐ๋ง ๊ณต์ ํ ์ ์์
- ViewState์์ BindingViewState๋ก ์ง์ ํ ๊ฐ๊ณผ ์์ ViewStore๋ก ๋ถํฐ ์ ๋ฌ ๋ฐ์ State๋ฅผ ๋ฐ์ธ๋ฉ ํด์ค
CartButton(store: self.store)
- ์์๋ทฐ์์ ํ์๋ทฐ๋ฅผ ์ฌ์ฉํ ๋๋ store๋ฅผ ์ ๋ฌํด ์ฃผ๋ฉด ๋๋ค.

'iOS ๐ > Architecture' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[TCA] TCA์์์ ๋น๋๊ธฐ ์ฒ๋ฆฌ (0) | 2023.12.21 |
---|---|
[TCA] Overriding Dependencies (feat. Dependency Injection) (0) | 2023.12.13 |
[SwiftUI/TCA] Scope (1) | 2023.11.30 |
[SwiftUI/TCA] Store and ViewStore (0) | 2023.11.29 |
[SwiftUI/TCA] Effect ๊ตฌํ๊ณผ ํ์ฉ (0) | 2023.11.29 |