iShaymus
iShaymus

Reputation: 532

SwiftUI - Dynamically add @State for UI Toggle

I am currently getting a list of sites from a Firebase Firestore and then returning them to a list in SwiftUI. Each list item has a label and Toggle. The list of sites is dynamic so could be anywhere from 1-30+. How can I create an @State or similar bindable to observe each toggle's state.

I am currently rendering to UI with the following

@State private var SiteA = false
Form {
     Section (header: Text("Select Sites")) {
     ForEach(siteData.sites) { site in
         HStack {
             Toggle(isOn: self.$SiteA) {
                 Text(site.name)
                 Spacer()
             }
         }
     }
     }
}

Sites are retrieved using a Bindable object

import SwiftUI
import Combine
import Firebase
import FirebaseFirestore

struct Message: Identifiable {
    var title: String
    var messageBody: String
    var sentBy: String
    var targetSite: String
    var expired: Bool
    var timeStamp: Timestamp
    var emergency: Bool
    var id: String
}

struct Site: Identifiable {
    var id: String
    var name: String
}

class FirestoreMessages : ObservableObject {
    var db = Firestore.firestore()
    var didChange = PassthroughSubject<FirestoreMessages, Never>()
    @Published var messages: [Message] = [] {
        didSet{ didChange.send(self) }
    }
    @Published var sites: [Site] = [] {
        didSet { didChange.send(self) }
    }

    func listen() {
        db.collection("messages")
            .whereField("expired", isEqualTo: false)
            .addSnapshotListener { (snap, error) in
            if error != nil {
                print("Firebase Snapshot Error: \(error?.localizedDescription ?? "")")
            } else {

                self.messages.removeAll()

                for doc in snap!.documents {
                    let title = doc["title"] as! String
                    let messageBody = doc["body"] as! String
                    let sentBy = doc["sentBy"] as! String
                    let targetSite = doc["targetSite"] as! String
                    let expired = doc["expired"] as! Bool
                    let timeStamp = doc["timeStamp"] as! Timestamp
                    let emergency = doc["emergency"] as! Bool
                    let id = doc.documentID

                    let message = Message(
                        title: title,
                        messageBody: messageBody,
                        sentBy: sentBy,
                        targetSite: targetSite,
                        expired: expired,
                        timeStamp: timeStamp,
                        emergency: emergency,
                        id: id)

                    self.messages.append(message)
                }
            }
        }
    }

    func getSites() {
        db.collection("sites")
            .order(by: "name", descending: false)
            .getDocuments() { (querySnapshot, err) in
            if let err = err {
                print("Error getting docs: \(err)")
            } else {
                self.sites.removeAll()

                for document in querySnapshot!.documents {
                    let doc = document.data()

                    let id = document.documentID
                    let name = doc["name"] as! String

                    let site = Site(id: id, name: name)

                    self.sites.append(site)
                }
            }
        }
    }
}

How can I create an @State unique to each list item to monitor their states individually?

Upvotes: 4

Views: 1574

Answers (1)

Michael Long
Michael Long

Reputation: 1092

The answer to your problem is composition. Move the HStack and enclosed Toggle to a SiteRow view where each row has its own State.

struct SiteRow: View {

    @State private var state: Bool = false
    private let site: Site

    init(_ site: Site) {
        self.site = site
        self.state = site.isOn
    }

    var body: some View {
        HStack {
            Toggle(isOn: self.$state) {
                Text(site.name)
                Spacer()
            }
        }
    }
}

Then...

ForEach(siteData.sites) { site in SiteRow(site) }

Upvotes: 5

Related Questions