Your @State private var toggle: Bool = false
doesn't make sense. You have many courses, not a single course. Each course should have it's own toggle on/off, which is what you started to do with:
struct Course: Codable, Identifiable {
var isToggled = false /// here!
...
}
To use this, you can reference each course
's isToggled
inside the ForEach, like this:
ForEach(courses) { course in
Section(header: Text(course.title).font(.system(size: 15, weight: .medium, design: .rounded)).foregroundColor(.blue)) {
ForEach(course.courseName, id: .name) { item in
/// here!
Toggle(isOn: course.isToggled, label: {
Text(item.name)
})
}
}
}
However, this won't work. course.isToggled
is a Bool
, not a Binding<Bool>
, which the Toggle expects.
Where can you get Binding<Bool>
? From the @State var courses: [Course]
, of course! sorry for pun
The Binding<>
part comes from the @State
declaration.
Properties that are marked with @State
, like your @State var courses: [Course]
, include a projectedValue
that has the Binding<>
type.
You can access the projectedValue
by adding a $
to the property. So, if you write $courses
, that will have type Binding<[Course]>
.
But, your toggle expects Binding<Bool>
, not Binding<[Course]>
.
This is where the Bool
part comes in.
You will need to replace the Binding's value, [Course]
, with a Bool
. Well, we had a Bool
before, right?
struct Course: Codable, Identifiable {
var isToggled = false /// this is a Bool!
Each course has a isToggled
, which is a Bool
. From earlier on in this answer, we got this inside the ForEach
:
ForEach(courses) { course in
...
/// getting the Bool, which unfortunately doesn't work (yet)
Toggle(isOn: course.isToggled, label: {
... We need to somehow combine the Binding<>
with the Bool
. This means that we must
- reference
$courses
(to get the Binding<>
)
- get each courses'
isToggled
And... tada!
$courses[index].isToggled /// has type Binding<Bool>
To get index
, we'll need to loop over courses.indices
instead of directly looping over courses
.
ForEach(courses.indices) { index in
...
/// this works!
Toggle(isOn: $courses[index].isToggled, label: {
Then, just replace every occurrence of course
in your old code's ForEach
with courses[index]
. Here's the full working example:
ForEach(courses.indices) { index in
Section(header: Text(courses[index].title).font(.system(size: 15, weight: .medium, design: .rounded)).foregroundColor(.blue)) {
ForEach(courses[index].courseName, id: .name) { item in
/// $courses[index].isToggled is a Binding<Bool>
Toggle(isOn: $courses[index].isToggled, label: {
Text(item.name)
})
}
}
}
As a convenience so you don't have to do courses[index]
every time you want the current course
, you can use Array(zip
as shown in this answer to loop over a (Int, Course)
. This also assigns a unique id
for every Section
inside the loop, so any transitions you add will work out smoothly.
ForEach(Array(zip(courses.indices, courses)), id: .1.id) { (index, course) in
Section(header: Text(course.title).font(.system(size: 15, weight: .medium, design: .rounded)).foregroundColor(.blue)) {
ForEach(course.courseName, id: .name) { item in
Toggle(isOn: $courses[index].isToggled, label: {
Text(item.name)
})
}
}
}
Well (Int, Course)
is actually (Range<Array<Course>.Index>.Element, Course)
but that's pretty much the same thing.
Final result:
Edit for isToggled
inside Content
, not Course
:
ForEach(Array(zip(courses.indices, courses)), id: .1.id) { (index, course) in
Section(header: Text(course.title).font(.system(size: 15, weight: .medium, design: .rounded)).foregroundColor(.blue)) {
ForEach(Array(zip(course.courseName.indices, course.courseName)), id: .1.id) { (itemIndex, item) in
/// here!
Toggle(isOn: $courses[index].courseName[itemIndex].isToggled, label: {
Text(item.name)
})
}
}
}