I have always placed the DisposeBag in ViewController in MVVM with RxSwift like it said in this topic:
On iOS, for the DisposeBag in MVVM, can it be placed in ViewModel?
But with combine, since the View is a struct and cancelable can't be placed in this, I am stuck with solution.
How to manage subscription between View and VM in Combine without add cancelable in ViewModel
Or maybe, in SwiftUI / Combine, there is no choice to place cancelables in VM.
There is an example of implementation in SiwftUI / Combine :
The ViewModel
class EquityViewModel: ObservableObject {
@Injected private var api: AlphaVantageAPI
private var cancellables = Set<AnyCancellable>()
private let code: String
@Published private var result: Quote?
@Published var price: String = ""
init(code: String) {
self.code = code
self.$result
.map {
return "\($0?.price ?? 0) €"
}.assign(to: &$price)
}
func addToPortfolio(){
}
func onAppear() {
self.api.quote(symbol: self.code).share()
.sink { completion in }
receiveValue: { quote in
self.result = quote.quote
}
.store(in: &cancellables)
}
}
The View
struct EquityView: View {
@ObservedObject var viewModel: EquityViewModel
init(viewModel: EquityViewModel) {
self.viewModel = viewModel
}
var body: some View {
ZStack {
Color("primary").edgesIgnoringSafeArea(.all)
VStack {
Text("Stock Price")
.foregroundColor(.white)
.frame(minWidth: 0,
maxWidth: .infinity,
alignment: .topLeading)
.padding()
HStack {
Text(self.viewModel.price)
.foregroundColor(.white)
Text("+4.75 %")
.foregroundColor(.white)
.padding(.leading, 20)
}.frame(minWidth: 0,
maxWidth: .infinity,
alignment: .topLeading)
.padding()
Button(action: self.viewModel.addToPortfolio, label: {
Text("Add to portfolio")
.foregroundColor(.white)
.frame(minWidth: 0,
maxWidth: .infinity,
maxHeight: 30,
alignment: .center)
.background(Color.blue)
.cornerRadius(5)
}).padding()
Spacer()
}
}.frame(alignment: .leading)
.onAppear(perform: self.viewModel.onAppear)
}
}