Search code examples
swiftswiftuicombinepublisher

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


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.


Solution

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