带有 CurrentValueSubject 绑定的 TextField 上的“Binding<String> 操作尝试每帧更新多次”

Posted

技术标签:

【中文标题】带有 CurrentValueSubject 绑定的 TextField 上的“Binding<String> 操作尝试每帧更新多次”【英文标题】:"Binding<String> action tried to update multiple times per frame" on TextField with CurrentValueSubject binding 【发布时间】:2021-10-09 09:04:47 【问题描述】:

我一直在尝试找到一种将TextField 与视图模型连接起来的好方法,以便TextField 的输入不会立即更新视图。

这是一个工作示例:

struct TextFieldSamples: View 
    @StateObject var vm = TFViewModel()

    var body: some View 
        print(Self._printChanges())
        return VStack 
            TextField("placeholder",
                      text: Binding(
                        get:  vm.outputText ,
                        set:  vm.subject.value = $0 
                      ))
            Text(vm.outputText) // this will only update when the textfield holds >5 characters
        
    


class TFViewModel: ObservableObject 
    @Published var outputText: String = ""
    let subject = CurrentValueSubject<String, Never>("")

    var subscriptions = Set<AnyCancellable>()

    init() 
        subject
            .filter( $0.count > 5 )
            .sink  self.outputText = $0 
            .store(in: &subscriptions)
    

但是,我在 swiftbysundell.com 博客上看到了下面的 propertyWrapped CurrentValueSubject

@propertyWrapper
struct Input<Value> 
    var wrappedValue: Value 
        get  subject.value 
        set  subject.send(newValue) 
    

    var projectedValue: AnyPublisher<Value, Never> 
        subject.eraseToAnyPublisher()
    

    private let subject: CurrentValueSubject<Value, Never>

    init(wrappedValue: Value) 
        subject = CurrentValueSubject(wrappedValue)
    

尝试使用,会报错Binding&lt;String&gt; action tried to update multiple times per frame。 (不仅如此,TextField 的输入行为也会变得混乱。这尤其体现在日语输入中,字符转换在大多数情况下都不起作用。)

这是一个显示此行为的示例:

struct TextFieldSamples: View 
    @StateObject var vm = TFViewModel()

    var body: some View 
        print(Self._printChanges())
        return VStack 
            TextField("placeholder", text: $vm.inputText)
            Text(vm.outputText)
        
    


class TFViewModel: ObservableObject 
    @Input var inputText: String = ""
    @Published var outputText: String = ""

    var subscriptions = Set<AnyCancellable>()

    init() 
        $inputText
            .filter( $0.count > 5 )
            .sink  self.outputText = $0 
            .store(in: &subscriptions)
    

我不太明白为什么 PropertyWrapper 版本没有按预期工作。

【问题讨论】:

你试过.debounce(for:)吗?将速率保持在导致错误的阈值以下可能就足够了。 谢谢@Yrb,我试过了。 Binding&lt;String&gt; 消息仍然显示。 【参考方案1】:

问题似乎是绑定到vminputText 字段和绑定到vm 本身作为@StateObject 的组合。

当输入文本时,它将值发送到inputText 绑定。这允许系统标记TextField 需要重新评估。

inputTextvm 对象的结构属性。因为vm@StateObject,当inputText发生变化时,vm对象广播TextFieldSamples视图需要重新评估。作为其中的一部分,它还尝试设置textInput 的值(设置为它已经拥有的值),这会导致“绑定在同一帧中多次触发”消息。

您可以通过将vm 设置为@State 属性而不是@StateObject 来消除绑定触发的额外时间。现在对inputText 的更改将触发一次,vm 不会广播第二次尝试触发它的对象更改消息。

但是,当您这样做时,系统不会知道 TextFieldSamples 需要重新评估,因此它永远不会看到对 outputText 的更改。

【讨论】:

以上是关于带有 CurrentValueSubject 绑定的 TextField 上的“Binding<String> 操作尝试每帧更新多次”的主要内容,如果未能解决你的问题,请参考以下文章

什么是 PassthroughSubject 和 CurrentValueSubject

CurrentValueSubject 和 @Published 之间的区别

Combine - 测试 CurrentValueSubject 是不是有订阅者

如何使用 Combine 的 CurrentValueSubject 并在 SwiftUI 视图中访问它?

数据绑定返回带有数据绑定的 html a href

带有条件的淘汰赛事件绑定