Home > front end >  Is there a way to update UIButton's titleLabel text using Combine's .assign instead of .si
Is there a way to update UIButton's titleLabel text using Combine's .assign instead of .si

Time:10-07

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)
  • Related