1

Hello! I want to display progress during my operation. I'm new to swift and swiftui. Please help... Here is the model with one async method:

class CountriesViewModel : ObservableObject {
    
    @Published var countries: [String] = [String]()
    @Published var isFetching: Bool = false
    
    @MainActor
    func fetch() async {
        
        countries.removeAll()
        
        isFetching = true
        
        countries.append("Country 1")
        Thread.sleep(forTimeInterval: 1)
        countries.append("Country 2")
        Thread.sleep(forTimeInterval: 1)
        countries.append("Country 3")
        Thread.sleep(forTimeInterval: 1)
        
        isFetching = false
    }
}

and the ContentView:

struct ContentView: View {
    
    @StateObject var countriesVM = CountriesViewModel()
    
    var body: some View {
        ZStack {
            if (countriesVM.isFetching) {
                ProgressView("Fetching...")
            }
            else {
                List {
                    ForEach(countriesVM.countries, id: \.self) { country in
                        Text(country)
                    }
                }
                .task {
                    await countriesVM.fetch()
                }
                .refreshable {
                    Task {
                        await countriesVM.fetch()
                    }
                }
            }
        }
    }
}

The progress is not displayed. What am I doing wrong?

1 Answer 1

4

you could try this simple approach:

struct ContentView: View {
    @StateObject var countriesVM = CountriesViewModel()
    
    var body: some View {
        ZStack {
            if (countriesVM.isFetching) {
                ProgressView("Fetching...")
            }
            else {
                List {
                    ForEach(countriesVM.countries, id: \.self) { country in
                        Text(country)
                    }
                }
                .refreshable {
                    Task {
                        await countriesVM.fetch()
                    }
                }
            }
        }
        .task {  // <-- here
            await countriesVM.fetch()
        }
    }
    
    class CountriesViewModel : ObservableObject {
        
        @Published var countries: [String] = [String]()
        @Published var isFetching: Bool = true  // <-- here
        
        @MainActor
        func fetch() async {
            
            countries.removeAll()
            
            isFetching = true
            
            countries.append("Country 1")
            Thread.sleep(forTimeInterval: 1)
            countries.append("Country 2")
            Thread.sleep(forTimeInterval: 1)
            countries.append("Country 3")
            Thread.sleep(forTimeInterval: 1)
            
            isFetching = false
        }
    }
}

EDIT-1: including a button to refresh the data.

struct ContentView: View {
    @StateObject var countriesVM = CountriesViewModel()
    
    var body: some View {
        
        ZStack {
            if countriesVM.isFetching {
                ProgressView("Fetching...")
            }
            else {
                VStack {
                    Button("refresh", action: {
                        Task { await countriesVM.fetch() }
                    }).buttonStyle(.bordered)
                    List {
                        ForEach(countriesVM.countries, id: \.self) { country in
                            Text(country)
                        }
                    }
                    .refreshable {
                        await countriesVM.fetch()
                    }
                }
            }
        }
        .task {
            await countriesVM.fetch()
        }
    }
    
    class CountriesViewModel : ObservableObject {
        
        @Published var countries: [String] = [String]()
        @Published var isFetching: Bool = false
        
        @MainActor
        func fetch() async {
            countries.removeAll()
            
            isFetching = true
            
            // representative background async process
            DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 2) {
                // eventualy, updates on the main thread for the UI to use
                DispatchQueue.main.async {
                    self.countries.append("Country 1")
                    self.countries.append("Country 2")
                    self.countries.append("Country 3")
                    self.isFetching = false
                }
            }
        }
    }
}
Sign up to request clarification or add additional context in comments.

4 Comments

Thanks... Yes, it works... But what if i have a button at ContentView for refreshing data? When i tap the button, no progressview showing...
well I cannot second guess what you have and what you have not in your code. All I can do is look at the code of your question. Show the code that truly represents what your problem is, or ask another question with the code you have a problem with.
updated my answer with a button to "refresh" the data. P.S, do not use Thread.sleep(forTimeInterval: 1) thinking it simulates a background task.
Thanks a lot! That is the needed behaviour! Thread.sleep was used to simulate hard work...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.