Swift/TCA

[TCA] Throttle, Debounce

insub4067 2023. 12. 24. 00:29
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
            }
        }
    }
}