SwiftUI NumberField提交超出范围值时更新异常问题求助
问题描述
我尝试基于SwiftUI的TextField构建一个NumberField组件,要求仅在触发.onSubmit修饰符时才验证输入值并通过Binding传递给外部。逻辑看似没问题,但提交超出指定范围的值时,NumberField无法正确更新显示。
原代码实现
struct NumberField: View { init( value: Binding<Double>, range: ClosedRange<Double> ) { self._value = value self.range = range self._valueStore = State(initialValue: value.wrappedValue) } @Binding var value: Double let range: ClosedRange<Double> @State private var valueStore: Double var body: some View { TextField( "", value: .init( get: { valueStore }, set: { newValue in valueStore = validate( newValue, range: range ) }), formatter: NumberFormatter() ) .onSubmit { value = valueStore } .onChange(of: value) { newValue in guard newValue != valueStore else { return } valueStore = newValue } } func validate(_ newValue: Double, range: ClosedRange<Double>) -> Double { let validatedValue = clamp(newValue, to: range) print("validate - newValue: \(newValue) - validated: \(validatedValue)") return validatedValue } func clamp(_ value: Double, to range: ClosedRange<Double>) -> Double { min(max(range.lowerBound, value), range.upperBound) } }
使用示例
struct MainView: View { @State var value = 2.0 var body: some View { VStack { Text("value: \(value)") NumberField(value: $value, range: 2.0 ... 10) NumberField(value: $value, range: 2.0 ... 10) } .frame(width: 200) .padding() } }
问题分析
原代码的核心问题在于输入阶段就对值做了验证:TextField的自定义Binding在setter中直接调用了validate方法,导致用户输入超出范围的值时,valueStore会被立刻修正为范围内的数值,这违背了“仅在提交时验证”的需求。同时,当多个NumberField绑定同一个外部值时,这种提前验证的逻辑会导致提交后的显示同步出现异常。
解决方案
调整逻辑,让输入阶段保留原始输入值,仅在.onSubmit触发时才执行验证并同步到外部Binding,同时更新本地存储的显示值:
struct NumberField: View { init( value: Binding<Double>, range: ClosedRange<Double> ) { self._value = value self.range = range self._valueStore = State(initialValue: value.wrappedValue) } @Binding var value: Double let range: ClosedRange<Double> @State private var valueStore: Double var body: some View { TextField( "", value: $valueStore, formatter: NumberFormatter() ) .onSubmit { let validatedValue = validate(valueStore, range: range) // 先更新外部绑定值 value = validatedValue // 同步更新本地存储,确保TextField显示验证后的值 valueStore = validatedValue } .onChange(of: value) { newValue in guard newValue != valueStore else { return } valueStore = newValue } } func validate(_ newValue: Double, range: ClosedRange<Double>) -> Double { min(max(range.lowerBound, newValue), range.upperBound) } }
说明
- 输入阶段:TextField直接绑定到
valueStore,允许用户输入任意合法数字(包括超出范围的值),输入内容会实时显示在TextField中。 - 提交阶段:触发
.onSubmit时,先对valueStore的值做范围验证,将验证后的结果同步到外部Binding的value,同时更新valueStore,确保TextField显示验证后的正确值。 - 外部值同步:当外部
value被其他组件修改时,.onChange会触发,将valueStore同步为新值,保证多个NumberField的显示一致。
内容的提问来源于stack exchange,提问作者Steve Begin
相关产品推荐
相关产品推荐

