How to avoid .task SwiftUI View Modifier call my async function every time the view is shown?

Viewed 371

Im building a movies app that has a list of movies and once an item is pressed, a detail view with the details of said movie is pushed on the navigation stack.

So im calling an async function that calls an API every 10 seconds and updates View values. Im calling it within a .task view modifier so it gets called as soon as the app runs which is perfect, problem is it is also running once the user returns from the detail view to the home view. How can I make it so it runs only once the app runs and then just after 10 seconds, regardless of wether the user navigates from one screen to another.

API call:

func getMovie() async {
    do {
        let movie = try await getMovieFromAPI()
        someMovie = movie
        try await Task.sleep(nanoseconds: 10_000_000_000)
        if !Task.isCancelled { 
           await getMovie()
        
        }
        } catch { 
        // Handle errors
        }
}

List on ContentView:

 List {
            ForEach(viewModel.movies) { movie in
                NavigationLink(destination: MovieDetail(movie: movie, viewModel: viewModel)) {
                    MovieCell(movie: movie, viewModel: viewModel)
                }
            }
        }
 .task {
        await viewModel.getMovie()
    }

Basically I just need to call an API once the view is shown and then only after 10 seconds.

1 Answers

I am assuming your viewmodel is an ObservableObject declared as Environment or State -Object.

Try adding this to your viewmodel:

var lastUpdated: Date = Date.distantPast
func getMovieDebounced() async{
    if lastUpdated.distance(to: Date()) > 10 {
        do{
          someMovie = try await getMovieFromAPI()
        } catch{
          //Handle error
        }
        lastUpdated = Date()
    }
}

this will check if 10 seconds have passed and call your getMovie() function. At the beginning lastUpdated is set to a value long ago, so it will trigger the first time.

And in the View:

.task {
    await viewModel.getMovieDebounced()
}
Related