在 SwiftUI / Combine 中捕获绑定的最后更改

Capture the Very Last Change of a Binding in SwiftUI / Combine

当我在 SwiftUI 中有一个绑定并且我想在绑定更改时保存(例如在 TextField 上)

var myText: String { /* value is derived */ }
func save(_ text: String) { /* doing the save stuff */ }
TextFiel(text: .init(get: { myText }, set: { save([=10=]) }) 

这样做,只要绑定发生变化,就会调用 save()。在某些情况下,这可能并不理想,例如当 save() 进行服务器调用或一些昂贵的计算时。所以我正在寻找的是,每当 binding 上次 更改时得到通知。 也许某种延迟的观察者在最终更改后触发 x 秒,如果另一个更改早于该阈值发生,则它会失效。 Combine 是否提供类似的服务?

免责声明:这个问题是关于一般的绑定,而不仅仅是特定的 TextField。 Textfield 只是一个编码示例,因此 .onCommit 不是我正在寻找的解决方案 ;)

Combine 中的 debounce 运算符就是这样做的。它会等到 X 时间内没有通过管道推送新值,然后发送信号。

TextField 的情况下,您仍然需要“正常”绑定,因为您希望用户看到实时出现的字符,即使数据没有显示立即发送到服务器(或任何其他昂贵的操作)。

import Combine
import SwiftUI

class ViewModel : ObservableObject {
    @Published var text : String = ""
    private var cancellables = Set<AnyCancellable>()
    
    init() {
        $text
            .debounce(for: .seconds(2), scheduler: RunLoop.main)
            .sink { (newValue) in
                //do expensive operation
                print(newValue)
            }
            .store(in: &cancellables)
    }
}

struct ContentView : View {
    @StateObject var vm = ViewModel()
    
    var body: some View {
        TextField("", text: $vm.text)
            .textFieldStyle(RoundedBorderTextFieldStyle())
            .padding()
    }
}

根据您的操作,您可能还想添加一个 .receive(on:) 运算符。要更新 UI,您需要在主线程上接收。但是,对于其他昂贵的操作,您可以通过这种方式将事情发送到后台队列。