Skip to content

Bug with SwiftUI binding passed features(TextEditor, TextField) #67

Description

@sobabear

Describe the bug 🐛

when using Binding, it cause some issues.

For instances, TextEditor and Textview always cursor jump to end thus developer must use @State rather than Reducer.State

Simulator.Screen.Recording.-.iPhone.15.Pro.-.2024-01-08.at.16.59.06.mp4
import SwiftUI
import Combine
import OneWay

struct TextEditorTest: View {
    @State var text1: String = ""

    // Oneway
    @StateObject var store1: ViewStore<OneWayEditorReducer1> = .init(reducer: .init(), state: .init())
    
    // Sample Oneway
    @StateObject var store2: SimpleStore<OneWayEditorReducer1> = .init(reducer: .init(), state: .init())
    
    
    var body: some View {
        VStack {
            HStack {
                // ✅ Works
                Text("1 state")
                   
                TextEditor1(tag: 1, text: $text1)
            }
            .background(Color.red)
            
            HStack {
                // ❎ Not Works
                Text("2 oneway")
                
                TextEditor1(tag: 3, text: .init(get: {
                    store1.state.text
                }, set: { value in
                    store1.send(.setText(value))
                }))
            }
            .background(Color.yellow)
            
            HStack {
                // ✅ Works
                Text("3 modified oneway")
                
                TextEditor1(tag: 2, text: .init(get: {
                    store2.state.text
                }, set: { value in
                    store2.send_return_state(.setText(value))
                }))
            }
            .background(Color.orange)

            
        }
    
    }
}

struct TextEditor1: View {
    var text: Binding<String>
    init(tag: Int,text: Binding<String>) {
        self.text = text
        print("💡 init tag:\(tag)")
    }
    
    var body: some View {
        TextEditor(text: text)
    }
}

class SimpleStore<R: Reducer>: ObservableObject {
    public typealias Action = R.Action
    public typealias State = R.State
    private let reducer: any Reducer<Action, State>
    @Published var state: State
    
    init(
        reducer: @Sendable @autoclosure () -> R,
        state: State
    ) {
        self.reducer = reducer()
        self.state = state
    }
    
    func send_return_state(_ action: Action) {
        if let reducer = reducer as? OneWayEditorReducer1 {
            self.state = reducer.reduce2(state: self.state as! OneWayEditorReducer1.State, action: action as! OneWayEditorReducer1.Action) as! R.State
        }
    }
    
}

class OneWayEditorReducer1: Reducer {
    enum Action {
        case setText(String)
    }
    
    struct State: Equatable {
        var text: String = ""
    }
    
    func reduce(state: inout State, action: Action) -> AnyEffect<Action> {
        
        switch action {
        case let .setText(value):
            state.text = value
            return .none
        }
    }
    
    func reduce2(state: State, action: Action) -> State {
        var newState = state
        switch action {
        case let .setText(value):
            newState.text = value
        }
        
        return newState
    }
}

To be honest, using inout function to modify state is my guess to this problem.

To Reproduce ✨

  1. Use TextEditor and make custom Binding using initializer to use store.send
  2. move cursor on TextEditor or TextField and type again, cursor moved to end

Expected behavior 🌈

Environments ⚙️

  • Mac OS: Every os
  • Device or Simulator OS: Every Device and simulaor
  • Xcode Version: 15.0.1
  • Library Version: 2.1.0

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions