skip to Main Content

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.

3

Answers


  1. 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()
                }
            }
        }
    }
    
    Login or Signup to reply.
  2. You don’t need Binding. You need ObservedObject.

    Login or Signup to reply.
  3. for anyone still wondering… it looks like this has been added

    .onContinuousHover(perform: { phase in
        switch phase {
        case .active(let location):
            print(location.x)
        case .ended:
            print("ended")
        }
    })
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search