I'm working on a calculator app but I'm having trouble displaying the results of a function. When I press a button in ButtonView
, a function runs to calculate the result but that result does not display in CalculationCircleView
.
Main View:
struct ScoreCalculatorView: View {
@StateObject private var viewModel = ViewModel()
var body: some View {
VStack {
Spacer()
HStack {
CalculationCircleView(calculation: viewModel.grossScore, label: "Score")
Spacer()
CalculationCircleView(calculation: viewModel.plusMinus, label: "+/-")
}
.padding(.horizontal, 30)
Spacer()
LazyVGrid(columns: viewModel.columns, spacing: 20) {
ForEach(0..<4, id: \.self) { index in
ZStack {
DataCellsView(labels: viewModel.scoreLabels[index])
TextField("\(viewModel.scoreLabels[index])", text: $viewModel.scoreData[index])
.font(.largeTitle)
.fontWeight(.light)
.frame(maxWidth: 80)
.multilineTextAlignment(.center)
.keyboardType(.numberPad)
}
}
}
.frame(width: 250)
Spacer()
ButtonView(data: viewModel.scoreData)
.padding(.bottom, 20)
}
}
}
CalculationCircleView:
struct CalculationCircleView: View {
@StateObject private var viewModel = ViewModel()
@State var result = ""
let calculation: Double
let label: String
var body: some View {
VStack {
ZStack {
Circle()
.stroke(lineWidth: 8)
.foregroundColor(.green)
.frame(height: 150)
Text("\(viewModel.grossScore, specifier: "%.1f")")
.font(.largeTitle)
.fontWeight(.light)
}
Text(label)
}
}
}
ButtonView:
struct ButtonView: View {
@StateObject private var viewModel = ViewModel()
let data: [String]
var body: some View {
Button(action: {
print("***** Score before func: \(viewModel.grossScore)")
viewModel.calculateGrossScore(data: data)
print("***** Score after func: \(viewModel.grossScore)")
// viewModel.calculatePlusMinus(data: data)
// viewModel.calculateDifferential(data: data)
}, label: {
ZStack {
RoundedRectangle(cornerRadius: 4)
.frame(width: 200, height: 50)
.foregroundColor(.green)
Text("Calculate")
.bold()
.foregroundColor(.white)
}
})
.padding(.bottom, 20)
}
}
I believe that the issue is the variable grossScore
is being updated after CalculationCircleView
has been called.
Is there anyway to update a view that is higher in the view hierarchy?
Note: There are some other bugs that I have not gotten to yet. Just wanted to focus on this one first.
Project Files: LINK
Hey there,
It seems like you're using @StateObject
in every single view. You should use @StateObject
once, here I would put it in your ContentView. This has to do with the source of truth in SwiftUI. You want there to exist only one source of truth, and then pass this object down to each of your views as an @ObservedObject
. This way, the data changed is observed and passed back to the original object (the one with the @StateObject
wrapper). Read more about this here: https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app
So, for example:
Main View
struct ScoreCalculatorView: View {
@StateObject private var viewModel = ViewModel()
var body: some View {
VStack {
Spacer()
HStack {
CalculationCircleView(viewModel: viewModel, calculation: viewModel.grossScore, label: "Score")
Spacer()
CalculationCircleView(viewModel: viewModel, calculation: viewModel.plusMinus, label: "+/-")
}
...
Child View
struct CalculationCircleView: View {
@ObservedObject var viewModel: ViewModel
@State var result = ""
let calculation: Double
let label: String
var body: some View {
The changes I have made here is first of all, changing @StateObject in your child views to be @ObservedObject, and second of all, passing in the viewModel as an argument when calling your child views in your main view.
Source of truth is very important in SwiftUI, it allows us to set a variable in one view and change it in another, and have those changes be synced across views. By having just one source of truth, we are able to determine the value of a piece of data, refer to it, and change it whenever necessary.