I have a @Published
string in my view model. I want to receive updates on its value in my view controller so that I can update the UI.
I am able to successfully get the updates through use of the sink
subscriber. This works fine:
viewModel.$buttonText.sink { [weak self] buttonText in
self?.buttonOutlet.setTitle(buttonText, for: .normal)
}.store(in: &cancellables)
But I am looking for a one line approach. Something more like what you are able to do with UILabels using the assign
subscriber:
viewModel.$title.assign(to: \.text, on: titleLabel).store(in: &cancellables)
I've tried accessing the buttonOutlet.titleLabel
directly, but this of course doesn't work since we can't directly update the text (UIButton.titleLabel
is read-only). And it also introduces the issue of unwrapping the titleLabel
property:
viewModel.$buttonText.assign(to: \.!.text, on: buttonOutlet.titleLabel).store(in: &cancellables)
I don't know if I'm just struggling to find the correct syntax or if this is simply a limitation of Combine for the time being.
CodePudding user response:
You can just write an extension:
extension UIButton {
var normalTitleText: String? {
get {
title(for: .normal)
}
set {
setTitle(newValue, for: .normal)
}
}
}
Now you can get the keypath
viewModel.$title.assign(to: \.normalTitleText, on: someButton).store(in: &cancellables)
CodePudding user response:
Another option:
extension UIButton {
func title(for state: UIControl.State) -> (String?) -> Void {
{ title in
self.setTitle(title, for: state)
}
}
}
So you don't have to write a different var
for each control state. It can be used like this:
viewModel.$buttonText
.sink(receiveValue: buttonOutlet.title(for: .normal))
.store(in: &cancellables)
CodePudding user response:
There's nothing wrong with the other answers, but if you just want the least code overall, you tighten up your code by capturing buttonOutlet
directly and using $0
as the closure argument.
viewModel.$buttonText
.sink { [b = buttonOutlet] in b?.setTitle($0, for: .normal) }
.store(in: &cancellables)