SwiftUI中使用ObservedObject时TextField货币格式化异常
SwiftUI中ForEach结合ViewModel使用TextField的异常问题
问题现象
在SwiftUI中使用ForEach遍历ViewModel中的数组,为每个元素绑定TextField时出现以下异常:
- 删除
21.0中的数字1时,内容自动舍入为$2.00 - 输入新数字时同样会自动舍入,且
TextField每次都会失去焦点 - 直接使用
@State变量绑定TextField则功能正常
问题代码
数据模型与ViewModel
struct TestItem: Hashable { let id = UUID() var name: String var value: Decimal? } class TestVM: ObservableObject { @Published var items = [ TestItem(name: "arry"), TestItem(name: "hehe", value: 21.0), TestItem(name: "hehe", value: 21.0), TestItem(name: "hehe", value: 21.0), TestItem(name: "hehe", value: 21.0), TestItem(name: "hehe", value: 21.0), TestItem(name: "hehe", value: 21.0), TestItem(name: "hehe", value: 21.0), TestItem(name: "hehe", value: 21.0), TestItem(name: "hehe", value: 21.0), TestItem(name: "hehe", value: 21.0), TestItem(name: "hehe", value: 21.0), TestItem(name: "hehe", value: 21.0), TestItem(name: "hehe", value: 21.0), TestItem(name: "hehe", value: 21.0), TestItem(name: "hehe", value: 21.0), TestItem(name: "hehe", value: 21.0) ] }
异常视图代码
struct ContentView: View { @StateObject var vm = TestVM() var body: some View { ScrollView{ LazyVStack{ ForEach($vm.items, id: \.self){$val in HStack{ Button { } label: { Image(systemName: "minus.circle.fill") .foregroundStyle(.red) } TextField("Value", value: $val.value, format: .currency(code: "USD")) .id(val.id) .keyboardType(.decimalPad) .textFieldStyle(.roundedBorder) .frame(maxWidth: 85) } .padding(.horizontal) } } } } }
正常对比代码
struct ContentView: View { @StateObject var vm = TestVM() @State var num: Decimal = 27.0 var body: some View { VStack { HStack { Button {} label: { Image(systemName: "minus.circle.fill") .foregroundStyle(.red) } TextField("Value", value: $num, format: .currency(code: "USD")) .keyboardType(.decimalPad) .textFieldStyle(.roundedBorder) .frame(maxWidth: 85) } .padding(.horizontal) } } }
问题原因
核心问题在于ForEach的id参数使用了\.self:
TestItem遵循Hashable,当value字段修改时,整个item的哈希值会发生变化- 这会导致
ForEach认为当前元素是一个全新的元素,从而销毁并重建对应的TextField视图 - 视图重建会引发焦点丢失,同时格式渲染逻辑被重复触发,导致输入内容被意外舍入
另外,额外给TextField设置.id(val.id)属于冗余操作,反而可能加剧视图识别的混乱。
修复方案
将ForEach的id改为使用TestItem的唯一标识\.id,确保元素内容变化时,视图不会被重建,只做内容更新:
struct ContentView: View { @StateObject var vm = TestVM() var body: some View { ScrollView{ LazyVStack{ // 改用\.id作为ForEach的标识 ForEach($vm.items, id: \.id){$val in HStack{ Button { } label: { Image(systemName: "minus.circle.fill") .foregroundStyle(.red) } TextField("Value", value: $val.value, format: .currency(code: "USD")) .keyboardType(.decimalPad) .textFieldStyle(.roundedBorder) .frame(maxWidth: 85) } .padding(.horizontal) } } } } }
修复说明
- 使用
\.id作为ForEach的唯一标识,即使item的value修改,id始终不变,ForEach会正确识别是同一个元素,仅更新视图内容而非重建 - 移除了冗余的
.id(val.id),避免视图标识冲突 - 修复后
TextField可以正常输入,不会丢失焦点,格式渲染也会正常工作
内容的提问来源于stack exchange,提问作者Eric Agredo
相关产品推荐
相关产品推荐

