有没有办法使用 Combine 的 .assign 而不是 .sink 来更新 UIButton 的 titleLabel 文本?

Is there a way to update UIButton's titleLabel text using Combine's .assign instead of .sink?

我的视图模型中有一个 @Published 字符串。我想在我的视图控制器中接收其值的更新,以便我可以更新 UI.

我能够通过使用 sink 订阅者成功获取更新。这很好用:

viewModel.$buttonText.sink { [weak self] buttonText in
      self?.buttonOutlet.setTitle(buttonText, for: .normal)
}.store(in: &cancellables)

但我正在寻找一种单线方法。更像是您可以使用 assign 订阅者对 UI 标签执行的操作:

viewModel.$title.assign(to: \.text, on: titleLabel).store(in: &cancellables)

我试过直接访问 buttonOutlet.titleLabel,但这当然行不通,因为我们不能直接更新文本(UIButton.titleLabel 是只读的)。它还引入了展开 titleLabel 属性:

的问题
viewModel.$buttonText.assign(to: \.!.text, on: buttonOutlet.titleLabel).store(in: &cancellables)

我不知道我是否只是在努力寻找正确的语法,或者这是否只是目前 Combine 的一个限制。

你可以写一个扩展:

extension UIButton {
    var normalTitleText: String? {
        get {
            title(for: .normal)
        }
        set {
            setTitle(newValue, for: .normal)
        }
    }
}

现在你可以得到keypath

viewModel.$title.assign(to: \.normalTitleText, on: someButton).store(in: &cancellables)

另一个选项:

extension UIButton {
    func title(for state: UIControl.State) -> (String?) -> Void {
        { title in
            self.setTitle(title, for: state)
        }
    }
}

因此您不必为每个控件状态编写不同的var。可以这样使用:

viewModel.$buttonText
    .sink(receiveValue: buttonOutlet.title(for: .normal))
    .store(in: &cancellables)

其他答案没有错,但如果您只想要最少的代码,可以通过直接捕获 buttonOutlet 并使用 [=12=] 作为闭包参数来收紧代码。

viewModel.$buttonText
    .sink { [b = buttonOutlet] in b?.setTitle([=10=], for: .normal) }
    .store(in: &cancellables)