BLACK FRIDAY SALE: Save big on all my Swift books and bundles! >>

SOLVED: How to pass nested properies as a binding?

Forums > SwiftUI

Hi all - somewhat new to swift, but have been developing apps in the React Native world for a few years now.

I completed the iDine app project and have been making it my own by creating a workout logging app. I am running into an issue where I have a class Workout that is comprised Exercises that have Sets, and when rendering a workout I am iterating on each Set. I want to be able to pass the Sets weight to a TextField so that users can update this.

I have this working currently, but through a seemingly nasty way where I pass the entire workouts array and access the correct exercise/set via indexing the arrays. This feels wrong. How can or should I pass the weight and reps of the Set to my SetRow view as a binding?

Below you will find my structs

enum ExerciseMovements: String, Codable {
    case squat = "Squat"
    case benchPress = "Bench Press"
    case deadlift = "Deadlift"
}

struct Set: Codable, Equatable, Identifiable {
    var id: UUID
    var weight: String
    var reps: String
}

struct Exercise: Equatable, Identifiable {
    var id: UUID
    var date: Date
    var type: ExerciseMovements
    var sets: [Set]
}

Below is how I am building out the WorkoutView

struct WorkoutDetailView: View {
    @State private var workoutName = "New Workout"
    @State private var exercises: [Exercise] = []

    func addExercise() {
        let exerciseToAdd = Exercise(id: UUID(), date: Date(), type: .benchPress, sets: [])

        exercises.append(exerciseToAdd)
    }

    func addSet(exercise: Exercise) {
        if let index = exercises.firstIndex(of: exercise) {
            let setToAdd = Set(id: UUID(), weight: "0", reps: "0")
            exercises[index].sets.append(setToAdd)
        }
    }

    func saveExercise() {
        print(exercises)
    }

    var body: some View {
        ScrollView {
            VStack {
                ForEach(Array(exercises.enumerated()), id: \.offset) { exerciseIndex, exercise in
                    VStack {
                        HStack {
                            Text("Set")
                            Spacer()
                            Text("LBs")
                            Spacer()
                            Text("Reps")
                        }
                        ForEach(Array(exercise.sets.enumerated()), id: \.offset) { setIndex, set in
                            SetRow(exercises: $exercises, exerciseIndex: exerciseIndex, setIndex: setIndex)
                        }
                        .padding(.vertical)
                        .frame(maxWidth: .infinity)

                        Button("Add Set") {
                            addSet(exercise: exercise)
                        }
                    }
                }
                .padding()
                Button("Add Exercise") {
                    addExercise()
                }
            }
            .navigationTitle(workoutName)
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                Button("Finish") {
                    saveExercise()
                }
            }
        }
    }

And below is the SetRow

struct SetRow: View {
    @Binding var exercises: [Exercise];
     var exerciseIndex: Int;
     var setIndex: Int

    var body: some View {
        HStack {
            Text("\(setIndex + 1)")
            Spacer()
            TextField(exercises[exerciseIndex].sets[setIndex].weight, text: $exercises[exerciseIndex].sets[setIndex].weight)
                .keyboardType(.numberPad)
                .frame(width: 50)
            Spacer()

        }
    }
}

   

Perhaps there is a fancy way of doing this with a struct, but unfortunately I don't know one. As @State and @Binding are limited to View I would change Exercise to a class conforming to ObservableObject. In an ObservableObject you can declare properties as @Published. With this then you can pass it via $exercise.sets[setIndex].weight.

   

@Hatsushira thanks for the suggestion - I ended up splitting up my views into more logical steps and also moved my structs to classes per your suggestion. This cleaned my code up a TON.

1      

Hacking with Swift is sponsored by RevenueCat

SPONSORED In-app subscriptions are a pain to implement, hard to test, and full of edge cases. RevenueCat makes it straightforward and reliable so you can get back to building your app. Oh, and it's free if your app makes less than $10k/mo.

Learn more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.