Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI .task not invoking

Tags:

swift

swiftui

For some reason, the .task is not invoking my function automatically when the view gets open. If I call the function using Button("click") { Task { ... } }, then it works. Why does the .task not work here?

import SwiftUI

struct BookTextView: View {
    @StateObject var viewModel: BookTextViewModel = BookTextViewModel()

    var body: some View {
        Text("HELLO!")
        Group {
            switch viewModel.result {
            case .empty:
                EmptyView()
            case .loading:
                ProgressView()
            case .failure(let error):
                Text(error.localizedDescription)
                //            ErrorView(error: error, retryHandler: viewModel.load)
            case .success(let bookText):
                ScrollView {
                    VStack(spacing: 20) {
                        Text(bookText.text)
                    }
                    .padding()
                }
            }
        }.task {
            do {
                try await viewModel.load()
            } catch {
                
            }
        }
    }
}

My model

import SwiftUI

@MainActor class BookTextViewModel: ObservableObject {
    enum AsyncResult<Success> {
        case empty
        case loading
        case success(Success)
        case failure(Error)
    }
    
    @Published var bookText: BookText?
    @Published var result: AsyncResult<BookText> = .empty
    

    func load() async {
        print("loading?")
        ...

This is how i call the view from another component

 detail: {
            BookTextView()

like image 593
George Morris Avatar asked Sep 12 '25 03:09

George Morris


1 Answers

.task runs when the UI objects being described by the View model data structs appear on screen. EmptyView() describes a nothing UI, hence no UI object ever appears. So, try removing that and move your Text("HELLO!") into the .empty case and it should fix it.

.task will also run when it re-appears (e.g. going back) so you might want to check the model data is not already loaded so save needlessly loading it again. You could also position the .task strategically so it only runs in the situation there is no data, e.g. on a placeholder Text("Loading...") inside an if that checks if an optional state is nil.

Some tips to improve your code: put the text into a result enum instead of a separate var. Remove the object and just use @State var result because .task sort of already makes an object behind the scenes to manage the lifecycle. Declare your async fun somewhere else that is more easily testable by making it return something or throw.

like image 90
malhal Avatar answered Sep 14 '25 21:09

malhal