Guillaume
Guillaume

Reputation: 33

ProgressView updating on multiple steps of computation

I'm coding an app targeted at iOS but would be nice if it would still work (as is the case now) with iPadOS and macOS. This is a scientific app that performs matrices computations using LAPACK. A typical "step" of computation takes 1 to 5 seconds and I want the user to be able to run multiple computations with different parameters by clicking a single time on a button. For example, with an electric motor, the user clicks the button and it virtually performs the action of turning the motor, time step after time step until it reaches a final value. With each step, the “single step” computation returns a value, and the “multiple step” general computation gathers all these single step values in a list, the aim being to make a graph of these results.

So, my issue is making a progress bar so that the user knows where he is with the computation he issued. Using a for loop, I can run the multistep but it won’t refresh the ProgressView until the multistep computation is over. Thus, I decided to try DispatchQueue.global().async{}, in which I update the progress of my computation. It seems to work on the fact of refreshing the ProgressView but I get warnings that tell me I’m doing it the wrong way:

[SwiftUI] Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.

I do not know how to publish on ProgressView, also because all the examples I come across on the Internet show how to update ProgressView with a timer, and that is not what I want to do, I want each computation to be achieved, send ProgressView the fact that it can update, and continue with my computation.

Also, what I did does not update correctly the final values. As the computation is asynchronous, the multistep function finishes instantaneously, showing a value of 0.0 while when the tasks finishes, the final value should be 187500037500.0. I show you an example code with these values, it’s a dummy and I put a huge for loop to slow down the code and ressemble a computation so you can see the update of the ProgressView.


import SwiftUI



class Params: ObservableObject {
    /// This is a class where I store all my user parameters
    @Published var progress = 0.0
    @Published var value = 0.0
}




struct ContentView: View {
    @StateObject var params = Params()
    @FocusState private var isFocused: Bool
    var body: some View {
        NavigationView {
            List {
                Section("Electromagnetics") {
                    NavigationLink {
                        Form {
                            ViewMAG_MultiStep(isFocused: _isFocused)
                        }
                        .toolbar {
                            ToolbarItemGroup(placement: .keyboard) {
                                Spacer()
                                Button("Done") {
                                    isFocused = false
                                }
                            }
                        }
                    } label: {
                        Text("Multi-step")
                    }
                }
            }
            .navigationTitle("FErez")
        }
        .environmentObject(params)
    }
}

struct ViewMAG_MultiStep: View {
    @FocusState var isFocused: Bool
    @EnvironmentObject var p: Params
    @State private var showResults = false
    @State private var induction = 0.0
    
    var body: some View{
        List{
            Button("Compute") {
                induction = calcMultiStep(p: p)
                showResults.toggle()
            }
            .sheet(isPresented: $showResults) {
                Text("\(induction)")
                
                ProgressView("Progress...", value: p.progress, total: 100)
                
            }

        }
        .navigationTitle("Multi-step")
    }
}


func calcSingleStep(p: Params) -> Double {
    /// Long computation, can be 1 to 5 seconds.
    var induction = p.value
    for i in 0...5000000 {
        induction += Double(i) * 0.001
    }
    return induction
}

func calcMultiStep(p: Params) -> Double{
    /// Usually having around 20 steps, can be up to 400.
    var induction = 0.0
    DispatchQueue.global().async {
        for i in 0...5 {
            induction += Double(i) * calcSingleStep(p: p)
            p.progress += 10.0
        }
        print("Final value of induction: \(induction)")
    }
    return induction
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}


Upvotes: 1

Views: 1159

Answers (1)

RelativeJoe
RelativeJoe

Reputation: 5084

You have used the wrong thread, you should update the UI only on the main thread:

DispatchQueue.main.async { //main thread not global.

Please note: You should move those functions to your View struct or to a shared class, as using global functions does not align with best practices.

Upvotes: 1

Related Questions