You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 12:26:11