Dynamically Created List with Toggle

Hi,

I’m, trying to set up what could simply be thought of as an attendance tracker. I want a list of rows with a person’s name and then a toggle switch as to whether or not they are in attendance, I need this to be dynamically created because the list of names will be coming from a database. I then need to be able to store the names of those toggled to true in an array for use later.
Using SwiftUI and aiming to deploy to IOS 15

Any suggestions on this would be great.

I guess you would start with a struct like this:

struct Person
{
  var name: String
  var present: Bool
}

var list = [Person]() 

Then just append each name to the list.

Yea so that works and I can create the list from the data, however, i can’t work out how to have the toggle in each list element alter a boolean variable in the original list that it corresponds to.

I’ve currently got this which kind of works, except the toggle won’t function

    List(viewModel.members, id: \.id) { member in
        HStack{
            Text(member.firstName + " " + member.surname.prefix(1))
                .frame(alignment: .leading)
            Toggle("", isOn: member.$isPresent)
                .toggleStyle(.switch)
                .frame(alignment: .trailing)
        }
    }.onAppear {
        viewModel.getMembersForAttendence()

This is the error I get:
“Accessing State’s value outside of being installed on a View. This will result in a constant Binding of the initial value and will not update.”

I’m no expert, but I can suggest something that might work. Any help from more knowledgeable folks is welcome.

You need an @State variable for each member in the list. member.isPresent is not an @State variable.

Something like this:

@State private var memberIsPresent: Bool[] = []

Then in the .onAppear closure,

memberIsPresent.append(member.isPresent) 

for each member in the list.

Then Toggle("", isOn: $memberIsPresent[n]

where n is the index of the member (you may need a ForEach inside the List to get the index).

I know this isn’t a complete solution and there may be some errors due to my inexperience, but it should get you on the right track.

Here’s one way to do it. This is much easier in SwiftUI 3, since you can have bindable list elements. You have to be using Xcode 13 and compiling with Swift 5.5 for that to work, though.

Anyway…

struct SimplePerson: Identifiable, Equatable {
    var name: String
    var present: Bool
    var id: String { name }
}

class ToggleListViewModel: ObservableObject {
    @Published var people: [SimplePerson] = []
    
    init() {
        people = [
            SimplePerson(name: "Charlotte Grote", present: false),
            SimplePerson(name: "Shauna Wickle", present: true),
            SimplePerson(name: "Mildred Haversham", present: true),
            SimplePerson(name: "Linton Baxter", present: true),
            SimplePerson(name: "Jack Finch", present: false),
            SimplePerson(name: "Sonny Craven", present: true)
        ]
    }
}

//we use this extension on Sequence so we can get the index of each item
//during ForEach while still maintaining updateability of our array, which
//simply using .indices will not do
extension Sequence {
    func indexed() -> Array<(offset: Int, element: Element)> {
        return Array(enumerated())
    }
}

struct ToggleList: View {
    @StateObject var viewModel = ToggleListViewModel()
    
    var body: some View {
        List(viewModel.people.indexed(), id: \.1.id) { index, person in
            ToggleRow(person: $viewModel.people[index])
        }
    }
}

struct ToggleRow: View {
    @Binding var person: SimplePerson
    
    var body: some View {
        HStack {
            Text(person.name)
            Toggle("", isOn: $person.present)
        }
    }
}

Give this a try, see if it works for you.

Thank you, that works to create the list, however, how would I be able to access which toggles are set to true? And say store their name in an Array of strings?

You would just manipulate the people property of the ToggleListViewModel class like you would any array. Something like this, for instance:

let presentPeople = viewModel.people.filter(\.present)
//presentPeople will now be an array of Person objects
//who have present == true

or:

let whoIsPresent = viewModel.people.compactMap { $0.present ? $0.name : nil }
//whoIsPresent will now be an array of the names of persons who have
//present == true