I'm encountering difficulty updating items within a VStack in my SwiftUI app. Each item (ListItemView()) displays a label showing the remaining time. I'm struggling to find an approach to achieve this for each item in a VStack (specifically a LazyVStack) on the screen.
Here's how my ViewModel is structured:
@MainActor
final class MyViewModel: ObservableObject {
private var cancellables: Set<AnyCancellable> = []
private (set) var models: [MyModel] = []
@Published private(set) var listItemModels: [MyListItemModel] = []
init() {
Timer.publish(every: 1, on: .main, in: .common)
.autoconnect()
.sink { [weak self] _ in
self?.updateModelsTimeLeft()
}.store(in: &cancellables)
}
func fetchData(limit: Int) async {
do {
let models = try await APIService.fetchModels()
self.models = models
listItemModels = models.map { MyListItemModel(id: $0.id,
timeInMs: $0.timeInMs)}
} catch {
print("Error fetching models: \(error)")
}
}
}
private extension MyViewModel {
func updateModelsTimeLeft() {
for index in 0..<self.listItemModels.count {
self.listItemModels[index].timeInMs = // changed based on some logic
}
}
}
}
The MyListItemModel used in the ViewModel is defined as follows:
struct MyListItemModel: Identifiable {
let id: Int
var timeInMs: Int
}
I'm attempting to display these items on the screen using:
LazyVStack(alignment: .center, spacing: 16) {
ForEach(viewModel.listItemModels, id: \.id) { model in
ListItemView(model: model)
}
}
Inside the ListItemView, I'm simply printing the value inside a Text() view.
The problem happens with Timer publisher I guess: the UI doesn't update at all.
Any suggestions on how to properly update the UI in SwiftUI when using Timer publisher for this scenario would be greatly appreciated!
Edit:
Not sure how this can be important, but this is how I fetch data:
// This is on my main screen's view.
.onAppear {
Task {
await viewModel.fetchData(limit: 20)
}
}
To fetch data it's like this:
.taskremoves the need for a@StateObjectto manage the lifetime of the async work.To transform models into certain Views use a computed property like this:
I'll think about your timer, maybe another
.taskwith a while loop withTask.sleep.