1

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()
    }
}


Guillaume
  • 33
  • 5

1 Answers1

1

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.

Timmy
  • 4,098
  • 2
  • 14
  • 34