1

How do I pass a bindable object into a view inside a ForEach loop?

Minimum reproducible code below.

class Person: Identifiable, ObservableObject {
    let id: UUID = UUID()
    @Published var healthy: Bool = true
}


class GroupOfPeople {
    let people: [Person] = [Person(), Person(), Person()]
}

public struct GroupListView: View {
    
    //MARK: Environment and StateObject properties
    
    //MARK: State and Binding properties
    
    //MARK: Other properties
    let group: GroupOfPeople = GroupOfPeople()
    
    //MARK: Body
    public var body: some View {
        ForEach(group.people) { person in
            //ERROR: Cannot find '$person' in scope
            PersonView(person: $person)
        }
    }
    
    //MARK: Init
    
}

public struct PersonView: View {
    
    //MARK: Environment and StateObject properties
    
    //MARK: State and Binding properties
    @Binding var person: Person
    //MARK: Other properties
    
    
    //MARK: Body
    public var body: some View {
        switch person.healthy {
        case true:
            Text("Healthy")
        case false:
            Text("Not Healthy")
        }
    }
    
    //MARK: Init
    init(person: Binding<Person>) {
        self._person = person
    }
}

The error I get is Cannot find '$person' in scope. I understand that the @Binding part of the variable is not in scope while the ForEach loop is executing. I'm looking for advice on a different pattern to accomplish @Binding objects to views in a List in SwiftUI.

1
  • 3
    There is nothing in your example that requires you to pass a binding to your PersonView, so the simple answer is just to remove the @Binding and pass person. The more complex answer is probably that you need to think about your model object. You probably need more than a simple array, but you haven't explained why you think you need a Binding Commented Mar 22, 2022 at 20:05

2 Answers 2

3

The SwiftUI way would be something like this:

// struct instead of class
struct Person: Identifiable {
    let id: UUID = UUID()
    var healthy: Bool = true
}


// class publishing an array of Person
class GroupOfPeople: ObservableObject {
    @Published var people: [Person] = [
        Person(), Person(), Person()
    ]
}

struct GroupListView: View {
    
    // instantiating the class
    @StateObject var group: GroupOfPeople = GroupOfPeople()
    
    var body: some View {
        List {
            // now you can use the $ init of ForEach
            ForEach($group.people) { $person in
                PersonView(person: $person)
            }
        }
    }
}

struct PersonView: View {
    
    @Binding var person: Person

    var body: some View {
        HStack {
            // ternary instead of switch
            Text(person.healthy ? "Healthy" : "Not Healthy")
            Spacer()
            // Button to change, so Binding makes some sense :)
            Button("change") {
                person.healthy.toggle()
            }
        }
    }
}
Sign up to request clarification or add additional context in comments.

Comments

-1

It looks like this has been added:

.onContinuousHover(perform: { phase in
    switch phase {
    case .active(let location):
        print(location.x)
    case .ended:
        print("ended")
    }
})

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.