Accessing @State variables in child views from parent views SwiftUI

Viewed 4157

I am trying to access variables that are marked as @State in a view from the view that calls that view. These variables in the child view are hooked up to a text box. If I type in that text box and hit return and then click a button, the button is in the parent view, to read that variable it returns an empty string. If I take that button and put it inside the child view and do the same thing it prints what I typed in the text box. I am very new to SwiftUI and it is probably a very simple fix but I have tried everything. Any help is greatly appreciated!

Parent view (DetailedView)

struct DetailedView: View {
    
    @Environment(\.managedObjectContext) var moc
    
    var targetMuscle : String = "Chest"
    let today = Date()
    @State public var exerciseCards : [ExerciseCard] = []
    @State public var exercise : String = "Bench Press"
    @State public var exercises : Int = 0
    @State public var showPassedWorkouts : Bool = false
    
    static let taskDateFormat : DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateStyle = .long
        return formatter
    }()
    
    var body: some View {
        ZStack{
            VStack{
                HStack{
                    VStack{
                        Text(targetMuscle).font(.system(size:40)).fontWeight(.medium)
                        Text("\(today, formatter: Self.taskDateFormat)")
                            .font(.system(size:20))
                    }.frame(width: 250, height: 30, alignment: .topLeading)
                        .navigationBarTitle("")
                        .navigationBarHidden(true)
                        .padding(.bottom, -7)
                    Button(action: {
                        self.showPassedWorkouts.toggle()
                    }) {
                        Text("Passed Workouts")
                            .multilineTextAlignment(.center)
                        
                        
                    }.offset(x: -75, y: 25)
                        .sheet(isPresented: $showPassedWorkouts){
                            PassedWorkoutList()
                    }
                    
                    
                    Button(action: {
                        
                        let workout = Workout(context: self.moc)
                        workout.muscle = self.targetMuscle
                        workout.date = formattedDateString(day: self.today)
                        
                        for number in 0..<self.exerciseCards.count{
                            let exercise = Exercise(context: self.moc)
                            exercise.name = self.exerciseCards[number].exercise
                            for innerNum in 0..<self.exerciseCards[number].tableRows.count{
                                let exerciseSet = ExerciseSet(context: self.moc)
                                exerciseSet.reps = Int16(self.exerciseCards[number].tableRows[innerNum].reps) ?? 0
                                exerciseSet.weight = Int16(self.exerciseCards[number].tableRows[innerNum].weight) ?? 0
                                exerciseSet.set = self.exerciseCards[number].tableRows[innerNum].set
                                exercise.addToExerciseSet(exerciseSet)
                            }
                            workout.addToExercise(exercise)
                        }
                        
                        try? self.moc.save()
                        
                    }) {
                        Text("Finish")
                    }.offset(x: -20, y: 20)
                }.padding(.bottom, 35)
                    .padding(.leading)
                
                
                
                ScrollView{
                    ForEach(0..<exerciseCards.count, id: \.self){ number in
                        self.exerciseCards[number]
                    }
                    
                    
                    Button(action: {
                        self.exerciseCards.append(ExerciseCard())
                    }) {
                        Text("Add Exercise")
                            .frame(minWidth: 325)
                            .padding()
                            .foregroundColor(.white)
                            .background(Color.blue.opacity(0.7))
                            .cornerRadius(20)
                        
                    }.padding(.top)
                        .frame(width: 400)
                }
                
                
            }
        }.background(Color.white)
            
    }
    
}

First Child View(ExerciseCard)

struct ExerciseCard: View {
    @State public var exercise : String = ""
    @State public var tableRows : [ExerciseTableRow] = []
    
    var body: some View {
        VStack{
            TextField("Enter Exercise", text: $exercise).textFieldStyle(RoundedBorderTextFieldStyle())
            .frame(width: 300)
            .multilineTextAlignment(.center)
            HStack{
                Group{
                    Text("Set")
                    Text("Weight")
                    Text("Reps")
                }.padding(.horizontal, 30)
                .offset(x: -20, y: 0)
                
            }
            VStack{
                
                ForEach(0..<tableRows.count, id: \.self){ number in
                    self.tableRows[number]
                    
                    
                }
            }.padding(.bottom, 5)
            
            HStack{
                Button(action: {
                    if self.tableRows.count > 1{
                        self.tableRows.remove(at: self.tableRows.count-1)
                    }
                        
                }) {
                    Text("Remove Set")
                        .frame(minWidth: 150)
                        .padding(.vertical, 5)
                        .foregroundColor(.white)
                        .background(Color.red)
                        .cornerRadius(20)
                    
                }
                
                Button(action: {
                    self.tableRows.append(ExerciseTableRow(set: 2, readOnly: false, setWeight: 2, setReps: 2))
                }) {
                    Text("Add Set")
                        .frame(minWidth: 150)
                        .padding(.vertical, 5)
                        .foregroundColor(.white)
                        .background(Color.green)
                        .cornerRadius(20)
                    
                }
                
            }
        }
        .padding()
        .padding(.vertical)
        .background(Color.offWhite)
        .cornerRadius(20)
        .shadow(color: Color.black.opacity(0.2), radius: 10, x:10, y:10)
        .shadow(color: Color.white.opacity(0.7), radius: 10, x:-5, y:-5)
    }
}

Second Child view(ExerciseTableRow)

struct ExerciseTableRow: View {
    @State public var weight : String = "0"
    @State public var reps : String = "0"
    var set : Int16
    var readOnly : Bool
    var setWeight : Int16
    var setReps : Int16
    var body: some View {
        
        HStack{
            Text(String(set))
                .padding(.trailing, 40)
                .padding(.leading, 10)
            if readOnly == false{
                Group{
                    TextField("0", text: $weight)
                    TextField("0", text: $reps)
                }.textFieldStyle(RoundedBorderTextFieldStyle())
                    .frame(width: 50)
                    .multilineTextAlignment(.center)
                    .keyboardType(.numberPad)
                    .padding(.horizontal, 30)
            }
            else{
                Group{
                    Text(String(setWeight))
                    Text(String(setReps))
                }
                .frame(width: 50)
                    .overlay(
                        RoundedRectangle(cornerRadius: 5)
                            .stroke(Color.black, lineWidth: 1)
                    )
                    .padding(.bottom, 5)
                    .padding(.horizontal, 30)
            }
            
        }
        
        
    }
}
2 Answers

You can declare your child view properties as @Binding instead of @State:

struct DetailedView: View {
    ...
    @State public var exercise: String = "Bench Press"
}

struct ExerciseCard: View {
    @Binding public var exercise: String
    ...
}

and then pass the variable from the parent view to the child view:

ExerciseCard(exercise: $exercise)

the State in your DetailedView and the one in your ExerciseTableRow are not related. they just happen to have the same name but they're different.

To have access to those variables in your child view, you have to pass those values to your child view.

you can just store them in a constant:

struct ExerciseCard: View {
    let exercise : String
    let tableRows : [ExerciseTableRow]

    // 
}

and initialize your view by passing values:

ExerciseCard(exercise: someString, tableRows: someArray)

if you want to be able to change those values inside the child view, you should define them as Bindings in the child view:

struct ExerciseCard: View {
    @Binding public var exercise : String
    @Binding public var tableRows : [ExerciseTableRow]
    //
}

and initialize it:

ExerciseCard(exercise: self.$exerciseString, tableRows: self.$tableRowsArray)

don't forget the leading $ before the State variable name to pass it as a Binding

Related