UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

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

        }
    }
}

1      

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.

1      

@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.

2      

TAKE YOUR SKILLS TO THE NEXT LEVEL If you like Hacking with Swift, you'll love Hacking with Swift+ – it's my premium service where you can learn advanced Swift and SwiftUI, functional programming, algorithms, and more. Plus it comes with stacks of benefits, including monthly live streams, downloadable projects, a 20% discount on all books, and free gifts!

Find out more

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

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

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.