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      

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

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.