Don't know how to explain it better. I'm trying to outline the code, because it's a bunch of lines.
- I have a View with a form showing an import button.
- I have a ViewModel (according to Paul's approach), that holds basically all of the states as
@Published var
s that I need, as well as functions to execute.
- I have a
struct SomeAPI
that provides special functions to fetch data.
The API fetchCollection
method is a bit complex. It's using Combine, reacting on queued status with retry, etc.
It also holds an enum
describing the status, such a call is in.
My problem is: I want to show the status of such a call in the view.
struct SomeAPI {
enum FetchStatus: String {
case none, started, queued, loaded
}
/// Provides a publisher for one specific fetch call
///
/// here: also receive some status to be set inside the chain of combine calls.
func fetchCollection(_ url: String) -> AnyPublisher<[CollectionItem], Never> {
return URLSession.shared.dataTaskPublisher(for: url)
.retry(1)
.tryMap({
// reacting on status codes 202, 429, etc. and throwing appropriate errors
})
.catch({ (error: Error) -> AnyPublisher<Result<URLSession.DataTaskPublisher.Output, Error>, Error> in
// reacting on retryable errors, then delaying and failing
// here I want to set the status, e.g. to .queued
})
// some more work, where I want to set status to other intermediate states.
.eraseToAnyPublisher()
}
}
extension AddFromCollectionView {
@MainActor class ViewModel: ObservableObject {
// declare 2 independent status variables because I have 2 fetches running in parallel.
@Published var gamesStatus: SomeAPI.FetchStatus = .none
@Published var expansionsStatus: SomeAPI.FetchStatus = .none
// holding the merged result
@Published var collectionItems = [CollectionItem]()
func importCollection() {
var fetches = [
SomeAPI.shared.fetchCollection(url1), // here, also pass gamesStatus
SomeAPI.shared.fetchCollection(url2) // here, also pass expansionsStatus
]
fetches.publisher.flatMap({ $0 })
.collect()
.sink(receiveCompletion: { completion in
print(completion)
}, receiveValue: { values in
let allItems = values.joined()
self.collectionItems = allItems.sorted { $0.id > $1.id }
})
.store(in: &cancellables)
}
}
}
The view shows a list of viewModel.collectionItems
just fine.
Now I want to show a progress bar reflecting the status of the individual requests (started, queued, loaded) because queue means retry with a delay,
but I struggle with how to pass the variables to the fetchCollection
function.
Binding doesn't work and is wrong anyways since it's a SwiftUI thing and shouldn't be used inside an isolated, view independent struct.
I can declare an inout
parameter in fetchCollection
, but as soon as I set the variable, I'm getting
Escaping closure captures 'inout' parameter 'status'
I read it may be because status is an enum (value type).
So: how can I have a status update reflected in the view, without moving all functions into the view or view model (where I would have direct access to the published vars)?