struct ContentView: View {
@State var store = Store(initialState: Content.State()) {
Content()
}
@FocusState var focused: Content.FocusItem?
var body: some View {
WithViewStore(store, observe: { $0 }) { viewStore in
VStack {
TextFieldComponent(
text: viewStore.$text1,
placeholder: "Placeholder"
)
.focused($focused, equals: .text1)
TextFieldComponent(
text: viewStore.$text2,
placeholder: "Placeholder"
)
.focused($focused, equals: .text2)
TextFieldComponent(
text: viewStore.$text3,
placeholder: "Placeholder"
)
.focused($focused, equals: .text3)
Spacer()
Button("Button") {
viewStore.send(.didTapButton)
}
.font(.title)
.foregroundStyle(Color.white)
.padding()
.background(
viewStore.isActive
? Color.blue
: Color.gray.opacity(0.3)
)
.cornerRadius(8)
.disabled(!viewStore.isActive)
}
.bind($focused, to: viewStore.$focused)
.onAppear { viewStore.send(.onAppear) }
}
}
}
@Reducer
struct Content {
struct State: Equatable {
@BindingState var text1 = ""
@BindingState var text2 = ""
@BindingState var text3 = ""
@BindingState var focused: FocusItem? = .none
var isActive: Bool {
!text1.isEmpty &&
!text2.isEmpty &&
!text3.isEmpty
}
}
enum FocusItem {
case text1, text2, text3
}
enum Action: BindableAction {
case binding(BindingAction<State>)
case onAppear
case didTapButton
case debouncedText
}
enum ID: Hashable {
case debounce, throttle
}
var body: some Reducer<State, Action> {
BindingReducer()
.onChange(of: \.$text1) { oldValue, newValue in
Reduce { state, action in
return .run(operation: { send in
await send( .debouncedText)
})
.throttle(
id: ID.throttle,
for: 0.5,
scheduler: DispatchQueue.main,
latest: true
)
.debounce(
id: ID.debounce,
for: 0.5,
scheduler: DispatchQueue.main
)
}
}
Reduce { state, action in
switch action {
case .onAppear:
state.focused = .text1
return .none
case .didTapButton:
return .none
case .debouncedText:
print(state.text1)
return .none
case .binding:
return .none
}
}
}
}