如何在SwiftUI的TableView中为TextField实现自动补全功能?
解决SwiftUI TableView中绑定数组对象的TextField自动补全失效问题
问题背景
需要给SwiftUI TableView中作为数据列的TextField添加自动补全功能,允许输入自定义值(不强制匹配建议项)。独立TextField的自动补全已实现,但当TextField绑定到ObservableObject数组中的对象属性时,封装的CompletingTextField内的.onChange闭包从未触发,建议项无法随输入更新。
问题根源
- 自定义Binding未正确关联到
Thing的@Published属性,导致输入变化无法被@Binding检测到 - 使用
@State存储建议项并依赖.onChange更新的方式,在TableView的cell环境下无法正常响应实时输入
解决方案
1. 简化自定义Binding,直接绑定到ObservableObject属性
在TableView的cell中,将Thing实例声明为@ObservedObject,直接使用其@Published属性的绑定,无需手动通过数组索引查找更新。
2. 重构CompletingTextField,移除.onChange依赖
不需要用@State存储建议项,直接在textInputSuggestions闭包中实时计算匹配的建议,避免依赖.onChange触发更新。
完整修正代码
数据模型(保持不变)
enum CommonLocation: String, CaseIterable, Identifiable { var id: Self { self } case Bathroom, Bedroom, DiningRoom, Foyer, Kitchen, LivingRoom } class Thing: Identifiable, Hashable, ObservableObject { static func == (lhs: Thing, rhs: Thing) -> Bool { lhs.id == rhs.id } @Published var name: String @Published var location: String let id: UUID = UUID() func hash(into hasher: inout Hasher) { hasher.combine(id) } init(title: String, location: String) { self.name = title self.location = location } } class ThingSet: ObservableObject, Identifiable { @Published var things: [Thing] = [] let id = UUID() init(things: [Thing]) { self.things = things } }
重构后的CompletingTextField
struct CompletingTextField: View { struct Choice: Identifiable { let value: String let id = UUID() init(_ value: String) { self.value = value } } @Binding var value: String private let allChoices: [String] init(value: Binding<String>, choices: [String]) { self._value = value self.allChoices = choices } private func matchingChoices() -> [Choice] { do { let filtered = try allChoices.filter(#Predicate<String> { $0.contains(value) }) return filtered.map(Choice.init) } catch { return [] } } var body: some View { TextField("", text: $value) .textInputSuggestions { ForEach(matchingChoices()) { choice in Text(choice.value) .textInputCompletion(choice.value) } } } }
修正后的ThingSetView
struct ThingSetView: View { @ObservedObject private var thingSet: ThingSet init(thingSet: ThingSet) { self.thingSet = thingSet } var body: some View { Table(of: Thing.self) { TableColumn("Name") { thing in @ObservedObject var observedThing = thing TextField("", text: $observedThing.name) } TableColumn("Location") { thing in @ObservedObject var observedThing = thing CompletingTextField( value: $observedThing.location, choices: CommonLocation.allCases.map(\.rawValue) ) } } rows: { ForEach(thingSet.things) { thing in TableRow(thing) } } } }
关键改动说明
- 绑定优化:在TableColumn的cell内部将
thing声明为@ObservedObject,确保Thing的@Published属性变化能实时传递到Binding,触发视图更新 - 移除.onChange:直接在
textInputSuggestions中实时计算匹配建议,无需依赖状态变量和.onChange闭包,避免了绑定更新不及时的问题 - 简化逻辑:去掉了冗余的
@State存储建议项的逻辑,让建议计算与输入值实时同步
内容的提问来源于stack exchange,提问作者Robert Fuhrer
相关产品推荐
相关产品推荐

