Search code examples
swiftiphoneswiftui

Concurrency background thread not working


I am currently learning about concurrency and would like to apply this knowledge to a basic calculation app I am developing. My goal is to ensure that two input calculation results are updated in real time, even while the calculations are being performed on a background thread.

However, my code doesn't work and does not update the result in real-time. I would greatly appreciate your assistance in resolving these issues.

Thank you in advance for your help. Below is my code.

class DataModel: ObservableObject {
    @Published var input1: Double? = nil
    @Published var input2: Double? = nil
    
    @Published var multiplication: Double = 0
    @Published var input1by2: Double = 0
    @Published var input2by1: Double = 0
    
    func multiply() async {
        guard
            let input1 = input1,
            let input2 = input2 
        else { return }
        
        try? await Task.sleep(nanoseconds: 1000)
        let value = input1 * input2
        await MainActor.run {
            self.multiplication = value
        }
    }
    
    func division1() async {
        guard
            let input1 = input1,
            let input2 = input2
        else { return }
        
        try? await Task.sleep(nanoseconds: 1000)
        let value = input1 / input2
        await MainActor.run {
            self.input1by2 = value
        }
    }
    
    func division2() async {
        guard
            let input1 = input1,
            let input2 = input2
        else { return }
        
        try? await Task.sleep(nanoseconds: 1000)
        let value = input2 / input1
        await MainActor.run {
            self.input2by1 = value
        }
    }
}


struct ContentView: View {
    var body: some View {
        ZStack {
            VStack (spacing: 100) {
                ResultView()
                
                UserInput()
            }
        }
        .padding()
    }
}


struct ResultView: View {
    
    @EnvironmentObject var tap: DataModel
    
    var body: some View {
        VStack(alignment: .leading, spacing: 10) {
            HStack(spacing: 0) {
                Text("Multiplication: ")
                Text("\(tap.multiplication, specifier: "%.2f")")
            }
            .task {
                await tap.multiply()
            }
            
            HStack(spacing: 0) {
                Text("Input1/Input2: ")
                Text("\(tap.input1by2, specifier: "%.2f")")
            }
            .task {
                await tap.division1()
            }
            
            HStack(spacing: 0) {
                Text("Input2/Input1: ")
                Text("\(tap.input2by1, specifier: "%.2f")")
            }
            .task {
                await tap.division2()
            }
        }
        .font(.title3)
        .foregroundStyle(Color.primary)
    }
}




struct UserInput: View {
    
    @EnvironmentObject var tap: DataModel
    
    var body: some View {
        VStack(spacing: 20) {
            HStack(spacing: 0) {
                Text("Input 1: ")
                    .padding(.horizontal, 6)
                
                TextField("123", value: $tap.input1, format: .number)
                    .foregroundStyle(Color.primary)
                    .keyboardType(.decimalPad)
            }
            .font(.headline)
            .frame(width: 200, height: 35)
            .background(
                RoundedRectangle(cornerRadius: 10)
                    .stroke(Color.blue.gradient, lineWidth: 2)
            
            )
            
            HStack(spacing: 0) {
                Text("Input 2: ")
                    .padding(.horizontal, 6)
                
                TextField("123", value: $tap.input2, format: .number)
                    .foregroundStyle(Color.primary)
                    .keyboardType(.decimalPad)
            }
            .font(.headline)
            .frame(width: 200, height: 35)
            .background(
                RoundedRectangle(cornerRadius: 10)
                    .stroke(Color.blue.gradient, lineWidth: 2)
            
            )
            
        }
    }
}

Solution

  • Try this approach removing the .task{...} from the ResultView and placing the equivalent in the UserInput when the user submit the input (in this example when the user submit the second input, the approach is flexible).

    Also note the use of @StateObject var dataModel = DataModel().

    class DataModel: ObservableObject {
        @Published var input1: Double? = nil
        @Published var input2: Double? = nil
        
        @Published var multiplication: Double = 0
        @Published var input1by2: Double = 0
        @Published var input2by1: Double = 0
        
        func multiply() async {
            guard
                let input1 = input1,
                let input2 = input2
            else { return }
            
            let value = input1 * input2
            await MainActor.run {
                self.multiplication = value
            }
        }
        
        func division1() async {
            guard
                let input1 = input1,
                let input2 = input2
            else { return }
            
            let value = input1 / input2
            await MainActor.run {
                self.input1by2 = value
            }
        }
        
        func division2() async {
            guard
                let input1 = input1,
                let input2 = input2
            else { return }
    
            let value = input2 / input1
            await MainActor.run {
                self.input2by1 = value
            }
        }
    }
    
    struct ContentView: View {
        @StateObject var dataModel = DataModel() // <--- here
        
        var body: some View {
            ZStack {
                VStack (spacing: 100) {
                    ResultView()
                    UserInput()
                }
            }
            .padding()
            .environmentObject(dataModel) // <--- here
        }
    }
    
    struct ResultView: View {
        
        @EnvironmentObject var tap: DataModel
        
        var body: some View {
            VStack(alignment: .leading, spacing: 10) {
                HStack(spacing: 0) {
                    Text("Multiplication: ")
                    Text("\(tap.multiplication, specifier: "%.2f")")
                }
                HStack(spacing: 0) {
                    Text("Input1/Input2: ")
                    Text("\(tap.input1by2, specifier: "%.2f")")
                }
                HStack(spacing: 0) {
                    Text("Input2/Input1: ")
                    Text("\(tap.input2by1, specifier: "%.2f")")
                }
            }
            .font(.title3)
            .foregroundStyle(Color.primary)
        }
    }
    
    struct UserInput: View {
        
        @EnvironmentObject var tap: DataModel
        
        var body: some View {
            VStack(spacing: 20) {
                HStack(spacing: 0) {
                    Text("Input 1: ")
                        .padding(.horizontal, 6)
                    
                    TextField("123", value: $tap.input1, format: .number)
                        .foregroundStyle(Color.primary)
                        .keyboardType(.decimalPad)
                }
                .font(.headline)
                .frame(width: 200, height: 35)
                .background(
                    RoundedRectangle(cornerRadius: 10)
                        .stroke(Color.blue.gradient, lineWidth: 2))
                
                HStack(spacing: 0) {
                    Text("Input 2: ")
                        .padding(.horizontal, 6)
                    
                    TextField("123", value: $tap.input2, format: .number)
                        .foregroundStyle(Color.primary)
                        .keyboardType(.decimalPad)
                }
                .onSubmit {  // <--- here
                    Task {
                        await tap.multiply()
                        await tap.division1()
                        await tap.division2()
                    }
                }
                .font(.headline)
                .frame(width: 200, height: 35)
                .background(RoundedRectangle(cornerRadius: 10)
                    .stroke(Color.blue.gradient, lineWidth: 2))
                
            }
        }
    }
    

    EDIT-1:

    with .onSubmit for input1

    struct UserInput: View {
        
        @EnvironmentObject var tap: DataModel
        
        var body: some View {
            VStack(spacing: 20) {
                HStack(spacing: 0) {
                    Text("Input 1: ")
                        .padding(.horizontal, 6)
                    
                    TextField("123", value: $tap.input1, format: .number)
                        .foregroundStyle(Color.primary)
                        .keyboardType(.decimalPad)
                }
                .onSubmit {  // <--- here
                    calculate()
                }
                .font(.headline)
                .frame(width: 200, height: 35)
                .background(
                    RoundedRectangle(cornerRadius: 10)
                        .stroke(Color.blue.gradient, lineWidth: 2))
                
                HStack(spacing: 0) {
                    Text("Input 2: ")
                        .padding(.horizontal, 6)
                    
                    TextField("123", value: $tap.input2, format: .number)
                        .foregroundStyle(Color.primary)
                        .keyboardType(.decimalPad)
                }
                .onSubmit {  // <--- here
                    calculate()
                }
                .font(.headline)
                .frame(width: 200, height: 35)
                .background(RoundedRectangle(cornerRadius: 10)
                    .stroke(Color.blue.gradient, lineWidth: 2))
                
            }
        }
        
        func calculate() { // <--- here
            Task {
                await tap.multiply()
                await tap.division1()
                await tap.division2()
            }
        }
    }