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

How do I pass in a struct's attributes as a Binding?

Forums > SwiftUI

For context I have the following HabitList Class and Habit Struct

struct Habit: Identifiable, Codable{
  let id = UUID()
  let name: String
  var hours: Double
  let description: String
}

class HabitList: ObservableObject{
  @Published var habitList = [Habit](){
  }
}

and in my ContentView I want to be able to change the hours attribute via Stepper in a ForEach Loop.

        ForEach(habitList.habitList){habit in
          Stepper("Hours: ", value: $habit.hours)
          }

However I am getting an error Cannot find '$habit' in scope , though I am not sure exactly why this particular message is showing up, I suspect that my issue is related to the fact that habit.hours is not a State Variable of the ContentView and thus I cannot pass it in like a binding..?

I cannot simply change my var hours: Double in the Habit struct to @State var hours: Double because then I get an error that the State attribute is unknown. I suspect that this is because Habit is not a view, but please correct me if that is incorrect.

I have managed to work with bindings before when I am binding a State property of the view, but I'm a little confused as to how I should bind an attribute of another type of struct.

I tried to be as clear as possible but let me know if anything needs clarification.

Thanks!

2      

You need to extract the Stepper to its own View and pass the entire Habit struct in as a Binding.

struct HabitTrackerView: View {
    @StateObject var habitList = HabitList()

    var body: some View {
        ForEach(Array(habitList.habitList.enumerated()), id: \.1.id) { index, habit in
            HabitStepperView(habit: $habitList.habitList[index])
        }
    }
}

struct HabitStepperView: View {
    @Binding var habit: Habit

    var body: some View {
        VStack {
            Text("\(habit.name) -- \(habit.hours)")
            Stepper("Hours: ", value: $habit.hours)
        }
    }
}

3      

You arent finding $habit because the ForEach is iterating over habitlist and returning a habit. To use the $ the habitlist would have to be a bindling itself.

3      

Thank you for your responses!

Just so that I am understanding, the moral of the story is that I the habitList object needs to be a Binding for the Habit objects in the loop to be bindings.

After some reflection it makes sense that for there to be a 2-way interaction for the Habit objects there needs to be a 2-way interaction for the habitList so it checks out!

2      

With Swift 5.5, this can be solved in a different way. (NOTE: I haven't actually tried this yet; I'm just going off of info from WWDC and Apple engineers on Twitter). Because of SE-0293, something like this should now be doable:

struct HabitTrackerView: View {
    @StateObject var habitList = HabitList()

    var body: some View {
        ForEach($habitList.habitList) { $habit in
            VStack {
                Text("\(habit.name) -- \(habit.hours)")
                Stepper("Hours: ", value: $habit.hours)
            }
        }
    }
}

And it's apparently back-portable to SwiftUI 2.0 (i.e., it will work on iOS14 devices) as long as you are using Swift 5.5 to compile.

I'll update this once I watch the Demystify SwiftUI session and after I get a chance to play with Swift 5.5 myself.

3      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

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.