Best data-binding practice in Combine + SwiftUI? Best data-binding practice in Combine + SwiftUI? ios ios

Best data-binding practice in Combine + SwiftUI?


An elegant way I found is to replace the error on the publisher with Never and to then use assign (assign only works if Failure == Never).

In your case...

dataPublisher    .receive(on: DispatchQueue.main)    .map { _ in "Data received" } //for the sake of the demo    .replaceError(with: "An error occurred") //this sets Failure to Never    .assign(to: \.stringValue, on: self)    .store(in: &cancellableBag)


I think the missing piece here is that you are forgetting that your SwiftUI code is functional. In the MVVM paradigm, we split the functional part into the view model and keep the side effects in the view controller. With SwiftUI, the side effects are pushed even higher into the UI engine itself.

I haven't messed much with SwiftUI yet so I can't say I understand all the ramifications yet, but unlike UIKit, SwiftUI code doesn't directly manipulate screen objects, instead it creates a structure that will do the manipulation when passed to the UI engine.


I ended up with some compromise. Using @Published in viewModel but subscribing in SwiftUI View.Something like this:

final class SwiftUIViewModel: ObservableObject {    struct Output {        var dataPublisher: AnyPublisher<String, Never>    }    @Published var dataPublisher : String = "ggg"    func bind() -> Output {        let dataPublisher = URLSession.shared.dataTaskPublisher(for: URL(string: "https://www.google.it")!)        .map{ "Just for testing - \($0)"}        .replaceError(with: "An error occurred")        .receive(on: DispatchQueue.main)        .eraseToAnyPublisher()        return Output(dataPublisher: dataPublisher)    }}

and SwiftUI:

struct ContentView: View {    private var cancellableBag = Set<AnyCancellable>()    @ObservedObject var viewModel: SwiftUIViewModel    init(viewModel: SwiftUIViewModel) {        self.viewModel = viewModel        let bindStruct = viewModel.bind()        bindStruct.dataPublisher            .assign(to: \.dataPublisher, on: viewModel)            .store(in: &cancellableBag)    }    var body: some View {        VStack {            Text(self.viewModel.dataPublisher)        }    }}