Search code examples
iosswiftswiftuicombine

SwiftUI - how to observe(.sink/ .onReceive) PassthroughSubject inside a View


If you encountered any of the next limitations while writing your app:

  1. The type of your ViewModel or Model is Struct not Class. This way you will not be able to use ObservableObject + ObservedObject.
  2. Also a second limitation is to avoid the use of Property Wrappers as: @Binding and @Published, in your Struct as they are harder to define as optional properties.
  3. You don't want bidirectional biding like the one resulting from using @State + @Binding. You want to be open - allow others to observ changes, but be closed - don't allow for other to make changes (Delegate pattern in UIKit, published pattern in RXSwift, @StateObject in SwiftUI but only for Class).

You could consider the next solution.


Solution

  • If you have a Struct as Model or ViewModel and you need to update your SwiftUI View here it is how to do just that with SwiftUI + Combine.

    import Combine
    
    struct ViewModel {
    
        var textChangedPublisher: AnyPublisher<String, Never> {
            textChangedSubject.eraseToAnyPublisher()
        }
        private let textChangedSubject = PassthroughSubject<String, Never>()
    
        func triggerUpdate() {
            let newStr = DateFormatter().string(from: Date())
            textChangedSubject.send(newStr)
        }
    
    }
    

    And a screen:

    import SwiftUI
    
    struct ContentView: View {
        
        private let vieModel = ViewModel()
        @State private var title = "Let's start"
        
        var body: some View {
            VStack {
                Text(title)
                Button.init("Show cuurent date") {
                    vieModel.triggerUpdate()
                }
                .onReceive(vieModel.textChangedPublisher) { newValue in
                    title = newValue
                }
            }
            .padding()
        }
    }