为什么在SwiftUI中绑定TextField时会调用两次didSet方法?

23

我有一个非常基本的视图,只显示一个TextField

视图

struct ContentView: View {

    @StateObject var viewModel = ViewModel()
    
    var body: some View {
        TextField("Enter a string...", text: $viewModel.string)
    }
    
}
TextField 的文本与视图模型上的 string 属性绑定:

视图模型

class ViewModel: ObservableObject {
    
    @Published var string: String = "" {
        didSet {
            print("didSet string:", string)
        }
    }
    
}

我添加了一个didSet属性观察者,在字符串更改时执行自定义操作。对于这个简单的示例,我只在控制台上打印一个字符串。

观察结果

当我运行这段代码并在文本字段中输入字符串“123”时,这是我得到的输出:

didSet string: 1
didSet string: 1
didSet string: 12
didSet string: 12
didSet string: 123
didSet string: 123

问题:

为什么?
每次我键入一个字符,didSet闭包会被调用两次?(我期望它每个字符只调用一次。)

这段代码有问题吗?还是这是预期的行为?


1
我记得在SwiftUI 1或2中,它根本没有被调用。如果您有敏感的副作用,请尝试使用属性发布者/合并。 - Asperi
1
有趣。你使用属性发布器是什么意思?通过 $string.sink {...} 监听更改? - Mischa
2
我遇到了同样的问题,但不知道为什么会发生这种情况。我知道在Swift中,didSet + property wrappers的行为很奇怪。也许这是一个bug?https://forums.swift.org/t/swift-5-2-struct-property-wrapper-didset-defect/34403/5 - Ilea Cristian
我有完全相同的问题。即使使用了 combine .. .$string.sink {...},该值也会出现两次。 - Stefan Nestorov
1
我也遇到了这个问题,在我的情况下,每当键入一个新字符时,文本字段也会查询我的数据库(以自动完成条目),因此我进行了两次必要的调用。有人已经向苹果提交了工单或在Swift论坛上发布了相关帖子吗? - Karrsen B
这里有几个可能的解决方案/变通方法:https://forums.swift.org/t/why-published-var-didset-is-called-extra-time-when-its-referenced-by-textfield-binding/52940/7 - Robin Macharg
2个回答

13
我在Xcode 14.2 RC和iOS 16.2 RC上遇到了这个问题,但奇怪的是添加.textFieldStyle(.plain).textFieldStyle(.roundedBorder)可以解决它。
我真的不确定为什么没有textFieldStyle会影响它,但是当我没有设置textFieldStyle时,绑定调用了set:{}两次,并且一旦我添加其中之一,它就会正常工作,并且每次只调用一次set:{}。
希望对某人有所帮助!

1
关于 .plain 的好发现,它“修复”了它... - Toma Radu-Petrescu
1
哇,如果不是如此荒谬,这将会很有趣。 - mylogon
仍然发生在iOS 16.5上。 - Joris Mans
对我来说,这个令人惊讶的行为也被移除了。然而,改变焦点仍然会触发更新(使用Combine)。通过在发布者上使用removeDuplicates()解决了这个问题(https://stackoverflow.com/a/63696674/2742007)。 - undefined

-1
 let binding = Binding<String>(get: {
                textvariable
            }, set: {
                viewModel.setText(query: $0) //add event inside setText
                // do whatever you want here
            })

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接