I'm relatively new to Swift and TCA, so I'm not totally sure if what I'm seeing is expected behavior or not.
The core thing that I'm trying to do is to format a string that is typed in to a TextField
and have the contents of that TextField
respect the formatting. Specifically, I want to format the input to a TextField
as a phone number.
My view looks more or less like
TextField(
"Phone Number",
text: viewStore.binding(
get: {
return $0.phoneNumber
},
send: FormAction.phoneNumberChanged
)
)
and my reducer looks like
case let .phoneNumberChanged(phoneNumberStr):
if phoneNumberStr.starts(with: "+1"), phoneNumberStr.count <= 3 {
// Condition A
return .none
}
state.phoneNumber = PartialFormatter().formatPartial(phoneNumberStr)
return .none
However, when the action fires, I noticed that when Condition A
occurs, the state displayed by the text field and the state of my application diverge. I would have expected the value that is displayed to be the get
value of the viewStore.binding
, but that does not seem to be the case.
Am I missing something about SwiftUI, Bindings, or TCA? Is this behavior expected?
I've included a complete pathological example, based on Binding Basics, of how to introduce this divergent state below. Notice that as you type in to the TextField
, it displays what you type, but State
remains unchanged. The behavior expected is that you should not be able to type into the TextField
.
// Based on https://github.com/pointfreeco/swift-composable-architecture/blob/master/Examples/CaseStudies/SwiftUICaseStudies/01-GettingStarted-Bindings-Basics.swift
import ComposableArchitecture
import SwiftUI
// The state for this screen holds a bunch of values that will drive
struct BindingBasicsState: Equatable {
var text = ""
}
enum BindingBasicsAction {
case textChange(String)
}
struct BindingBasicsEnvironment {}
let bindingBasicsReducer = Reducer<
BindingBasicsState, BindingBasicsAction, BindingBasicsEnvironment
> {
state, action, _ in
switch action {
case let .textChange(text):
// state.text = text
return .none
}
}
struct BindingBasicsView: View {
let store: Store<BindingBasicsState, BindingBasicsAction>
var body: some View {
WithViewStore(self.store) { viewStore in
Form {
Section(header: Text("Example")) {
HStack {
TextField(
"Type here",
text: viewStore.binding(get: { $0.text }, send: BindingBasicsAction.textChange)
)
.disableAutocorrection(true)
Text(viewStore.text)
}
}
}
}
.navigationBarTitle("Bindings basics")
}
}
struct BindingBasicsView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
BindingBasicsView(
store: Store(
initialState: BindingBasicsState(),
reducer: bindingBasicsReducer,
environment: BindingBasicsEnvironment()
)
)
}
}
}