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) } }}