Combine publishers: notify when ANY of the publishers changes a value

Viewed 1354

I'd like to trigger a "change" event on every change of either the username or password published properties and set a new Credentials published property derived of those two and emit an event.

What would be the simplest solution to achieve this result using SwiftUI & Combine?

Some sample code with the idea I'm trying to achieve:

import SwiftUI
import Combine
import Foundation

struct Credentials {
    let username: String
    let password: String

    init(username: String = "",
         password: String = "") {
        self.userName = username
        self.password = password
    }
}

final class ViewModel: ObservableObject {
    @Published var username = ""
    @Published var password = ""

    @Published var credentials = Credentials()


    init() {
        [$username, $password]// ..... What to do here? 
// How to "subscribe" each of those properties to emit an event
// so that I get notified each time one of them changes

        credentials = Credentials(username: $username, password: $password)
    }
}

Essentially, I'm looking to something similar to this answer: Swift Combine: How to create a single publisher from a list of publishers?

But with the notification should be triggered each time any of the publishers produce a value, not all of them.

1 Answers

Instead of using Publishers.MergeMany as in your linked question, you want to use .combineLatest(_:) on your first publisher, like so:

import SwiftUI
import Combine
import Foundation

struct Credentials {
    let username: String
    let password: String

    init(username: String = "",
         password: String = "") {
        self.userName = username
        self.password = password
    }
}

final class ViewModel: ObservableObject {
    @Published var username = ""
    @Published var password = ""

    @Published var credentials = Credentials()

    private var cancellable: Cancellable? = nil


    init() {
        cancellable = $username.combineLatest($password).sink { tuple in
            self.credentials = Credentials(username: tuple.0, password: tuple.1)
        }

        credentials = Credentials(username: username, password: password)
    }
}

(it's been a bit so this code may not run immediately, but hopefully you see where this is going).

Related