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