Firestore responding with "cannot find 'cards' in scope" - swift

I followed this tutorial to get data from firestore and changed what i needed to correspond to my model but it keeps responding with "cannot find 'cards' in scope" and I'm not sure what i did wrong. (i think i got the mvvm labels right)
VIEW
import SwiftUI
struct TestingView: View {
#ObservedObject private var viewModel = CardViewModel()
var body: some View {
List(viewModel.cards) {
Text(cards.name)
}
.onAppear() {
self.viewModel.fetchData()
}
}
}
VIEW MODEL
import Foundation
import Firebase
class CardViewModel: ObservableObject {
#Published var cards = [Cards]()
private var db = Firestore.firestore()
func fetchData() {
db.collection("cards").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
self.cards = documents.map { queryDocumentSnapshot -> Cards in
let data = queryDocumentSnapshot.data()
let name = data["name"] as? String ?? ""
let pronoun = data["pronoun"] as? String ?? ""
let bio = data["bio"] as? String ?? ""
let profileURLString = data["profileURLString"] as? String ?? ""
let gradiantColor1 = data["gradiantColor1"] as? UInt ?? 0
let gradiantColor2 = data["gradiantColor2"] as? UInt ?? 0
let gradiantColor3 = data["gradiantColor3"] as? UInt ?? 0
return Cards(name: name, pronoun: pronoun, bio: bio, profileURLString: profileURLString, gradiantColor1: gradiantColor1, gradiantColor2: gradiantColor2, gradiantColor3: gradiantColor3)
}
}
}
}
MODEL
import Foundation
struct Cards: Identifiable {
var id = UUID().uuidString
var name: String
var pronoun: String
var bio: String
var profileURLString: String
var gradiantColor1: UInt
var gradiantColor2: UInt
var gradiantColor3: UInt
var profileURL: URL {
return URL(string: profileURLString)!
}
}

List will provide an element to its trailing closure -- see card in in my code. Then, you can access that specific card in your Text element.
var body: some View {
List(viewModel.cards) { card in //<-- Here
Text(card.name) //<-- Here
}
.onAppear() {
self.viewModel.fetchData()
}
}
}
I'd suggest that you might want to rename the struct Cards to struct Card since it is one card. Then, your array would be #Published var cards = [Card]() -- ie an array of Cards. From a naming perspective, this would make a lot more sense.

Related

Function with a parameter inside init() not updating onAppear{}

Attempting to call a function within init(). I have a function that takes a parameter which I call in the initializer. I set the parameter to a blank string at first until I pass in the value in another view when it appears.
My issue is the function isn't updating immediately when the view first appears. My objective is to just have the function run immediately once the view is generated and have the view update immediately (where I display in my view values from the function).
My guess is since I'm passing in the initial blank string during the init(), my function isn't firing with the updated variable. I don't want to set the #State to #Binding as I don't want to have to pass in a value everytime I call the observedObject. Any advice is greatly appreciated.
To summarize my issue, when I call the grabItems recipe when my view first appears, it doesn't initially get called with the correct parameter (it gets called with a blank string).
class testClass: ObservableObject {
#State var uidNonUser: String
#Published var itemsNonUser = [Item]() // items saved
init(){
self.uidNonUser = ""
grabItems(userUID: uidNonUser) // << grabs things
}
}
struct testStruct: View {
#ObservedObject var rm = testClass()
#State var userUID: String // << variable passing to grabItems
var body: some View {
Text(rm.itemsNonUser.count)
.onAppear{
rm.grabItems(userUID: userUID)
}
}
}
FYI - pasting my actual grabItems recipe below just as reference in case it helps understand the issue.
func grabItems(userUID: String){
//grab user thats not current user
FirebaseManager.shared.firestore
.collection("users")
.document(userUID) // << passed in from view
.collection("userRecipes")
.addSnapshotListener { (snapshot, err) in
guard let documents = snapshot?.documents else{
print("no documents present")
return
}
self.itemsNonUser = documents.map { (querySnapshot) -> RecipeItem in
let data = querySnapshot.data()
let recipeTitle = data ["recipeTitle"] as? String ?? ""
let recipePrepTime = data ["recipePrepTime"] as? String ?? ""
let recipeImage = data ["recipeImage"] as? String ?? ""
let createdAt = data ["createdAt"] as? String ?? ""
let ingredients = data ["ingredientItem"] as? [String: String] ?? ["": ""]
let directions = data ["directions"] as? [String] ?? [""]
let recipeID = data ["recipeID"] as? String ?? ""
let recipeCaloriesMacro = data ["recipeCaloriesMacro"] as? Int ?? 0
let recipeFatMacro = data ["recipeFatMacro"] as? Int ?? 0
let recipeCarbMacro = data ["recipeCarbMacro"] as? Int ?? 0
let recipeProteinMacro = data ["recipeProteinMacro"] as? Int ?? 0
let recipe = RecipeItem(id: recipeID, recipeTitle:recipeTitle , recipePrepTime: recipePrepTime, recipeImage: recipeImage, createdAt: createdAt, recipeCaloriesMacro: recipeCaloriesMacro, recipeFatMacro: recipeFatMacro, recipeCarbMacro:recipeCarbMacro, recipeProteinMacro: recipeProteinMacro, directions: directions, ingredientItem: ingredients)
return recipe
}
}
}
Try this example code to update the View when it first appears.
// for testing
struct RecipeItem {
var recipeTitle: String
// ...
}
class TestClass: ObservableObject {
#Published var itemsNonUser = [RecipeItem]() // <-- here
func grabItems(userUID: String){
//...
// for testing
itemsNonUser = [RecipeItem(recipeTitle: "banana cake with ID: \(userUID)")]
}
}
struct TestStruct: View {
#StateObject var rm = TestClass() // <-- here
#State var userUID: String
var body: some View {
VStack {
Text(rm.itemsNonUser.first?.recipeTitle ?? "no name")
Text("count: \(rm.itemsNonUser.count)") // <-- here
}
.onAppear{
rm.grabItems(userUID: userUID) // <-- here
}
}
}
struct ContentView: View {
let userUID = "123456"
var body: some View {
TestStruct(userUID: userUID) // <-- here
}
}
In my instance (and could have been specific for my particular case) I updated the grabItems recipe to return an Int since my view was trying to display the count.
I then just called it directly into the view like so and it worked:
Text(String(rm.grabItems(userUID: userUID))).bold() // << calling function directly in view

Fails to update UI after firestore listener fires

I am trying to update my UI each time there have been changes in firestore document.
I when I check with console, I see that the listener fires each time I change document.
My listener and 'readyOrders' is #Published:
func getReadyOrders() {
referance
.collection(path)
.document(email)
.collection("CompletedOrders")
.whereField("placedBy", isEqualTo: user)
.addSnapshotListener { orderSnapshot, error in
DispatchQueue.main.async {
guard let snapshot = orderSnapshot?.documents else{
print("There is no active orders")
return
}
self.readyOrders = snapshot.map{ activeSnapshot -> ActiveOrder in
let data = activeSnapshot.data()
var collectedItems = [MenuItem]()
var collectedDrinks = [DrinkItem]()
let id = activeSnapshot.documentID
let placed = data["placedBy"] as? String ?? ""
let inZone = data["inZone"] as? String ?? ""
let forTable = data["forTable"] as? String ?? ""
let orderItems = data["orderItems"] as? [String]
let orderDrinks = data["orderDrinks"] as? [String]
let orderItemsReady = data["orderItemsReady"] as? Bool ?? false
let orderDrinksReady = data["orderDrinksReady"] as? Bool ?? false
let totalAmount = data["totalAmount"] as? Double ?? 0.00
orderItems?.forEach({ item in
let parts = item.components(separatedBy: "/")
collectedItems.append(MenuItem(itemName: parts[0], price: Double(parts[1])))
})
orderDrinks?.forEach({ drink in
let itemPart = drink.components(separatedBy: "/")
collectedDrinks.append(DrinkItem(drinkName: itemPart[0], price: Double(itemPart[1])))
})
return ActiveOrder(id: id,
placedBy: placed,
inZone: inZone,
forTable: forTable,
orderItems: collectedItems,
orderDrinks: collectedDrinks,
orderItemsReady: orderItemsReady,
orderDrinksReady: orderDrinksReady,
totalAmount: totalAmount)
}
}
}
}
View where I display all the documents
Note: This UI is updating when there is added new document or deleted current one.
Section {
ForEach(handler.readyOrders, id: \.id){ readyOrder in
NavigationLink{
OrderComplete(handler: handler, order: readyOrder, currency: currency)
} label: {
HStack{
Text(readyOrder.inZone!)
Text("- \(readyOrder.forTable!)")
}
}
}
} header: {
Text("Order's ready:")
}
And in this view I display the content of document, right in here the view does not update. To the file where I am displaying content I pass in the readyOrder from 'ForEach' and there I take the array in ready order and display it in 'ForEach':
ForEach(order.orderItems!, id:\.id){ item in
HStack{
Text(item.itemName!)
Spacer()
Text(currency.format(item.price!))
.foregroundColor(.teal)
Image(systemName: "arrow.left")
.foregroundColor(.red)
}
}
.onDelete(perform: deleteItem)
I have tried many things, and I am sure there is a simple solution, that I dont quite get. Because I am new to SwiftUI.
Edit:
I have puted together the the code for minimal repruduction as requested so there would more context for what I am trying to do.
Model:
struct Order: Identifiable{
var id = UUID().uuidString
var placedBy: String?
var inZone: String?
var forTable: String?
var orderItems: [MenuItem]?
var orderDrinks: [DrinkItem]?
var orderItemsReady: Bool?
var orderDrinksReady: Bool?
var totalAmount: Double?}
ViewModel:
class ViewModel: ObservableObject{
#Published var orders = [Order]()
private var referance = Firestore.firestore()
func getReadyOrders() {
referance
.collection(path)
.document(email)
.collection("CompletedOrders")
.whereField("placedBy", isEqualTo: user)
.addSnapshotListener { orderSnapshot, error in
DispatchQueue.main.async {
guard let snapshot = orderSnapshot?.documents else{
print("There is no active orders")
return
}
self.readyOrders = snapshot.map{ activeSnapshot -> ActiveOrder in
let data = activeSnapshot.data()
var collectedItems = [MenuItem]()
var collectedDrinks = [DrinkItem]()
let id = activeSnapshot.documentID
let placed = data["placedBy"] as? String ?? ""
let inZone = data["inZone"] as? String ?? ""
let forTable = data["forTable"] as? String ?? ""
let orderItems = data["orderItems"] as? [String]
let orderDrinks = data["orderDrinks"] as? [String]
let orderItemsReady = data["orderItemsReady"] as? Bool ?? false
let orderDrinksReady = data["orderDrinksReady"] as? Bool ?? false
let totalAmount = data["totalAmount"] as? Double ?? 0.00
orderItems?.forEach({ item in
let parts = item.components(separatedBy: "/")
collectedItems.append(MenuItem(itemName: parts[0], price: Double(parts[1])))
})
orderDrinks?.forEach({ drink in
let itemPart = drink.components(separatedBy: "/")
collectedDrinks.append(DrinkItem(drinkName: itemPart[0], price: Double(itemPart[1])))
})
return ActiveOrder(id: id,
placedBy: placed,
inZone: inZone,
forTable: forTable,
orderItems: collectedItems,
orderDrinks: collectedDrinks,
orderItemsReady: orderItemsReady,
orderDrinksReady: orderDrinksReady,
totalAmount: totalAmount)
}
}
}
}
func delteItem(menuItem: MenuItem, from order: ActiveOrder){
let item = menuItem.itemName! + "/" + String(menuItem.price!)
let pathTo = referance.collection(path).document(email).collection("CompletedOrders").document(order.id)
pathTo.getDocument { snapshot, error in
if let document = snapshot, document.exists{
var items = document.data()!["orderItems"] as? [String] ?? []
let index = items.firstIndex(where: { $0 == item })
items.remove(at: index!)
pathTo.updateData(["orderItems" : items]){ error in
if let _ = error{
print("Error deleting and updating order array")
}
}
}
}}}
And the veiws:
struct View1: View{
#ObservedObject var viewModel: ViewModel
var body: some View{
VStack{
ForEach(viewModel.orders, id: \.id){ readyOrder in
NavigationLink{
View2(viewModel: viewModel, order: readyOrder)
} label: {
HStack{
Text(readyOrder.inZone!)
Text("- \(readyOrder.forTable!)")
}
}
}
}
}}
struct View2: View{
#ObservedObject var viewModel: ViewModel
var order: Order
func deleteItem(at offest: IndexSet){
let index = offest[offest.startIndex]
let deleteItem = order.orderItems![index]
handler.delteItem(menuItem: deleteItem, from: order)
}
//In this view I want to get updated elements from document to display -> or if removed.
var body: some View{
VStack{
ForEach(order.orderItems!, id:\.id){ item in
HStack{
Text(item.itemName!)
Spacer()
Text(item.price!)
.foregroundColor(.teal)
Image(systemName: "arrow.left")
.foregroundColor(.red)
}
}
.onDelete(perform: deleteItem)
}
}}
SOLVED
I literally dont know why but when I changed my refarance from:
#ObservedObject var viewModel: Viewmodel
//To the stateobject
#StateObject var viewModel: ViewModel
As I understand the #StateObject in swift is used first time initializing view model class, and then you should use #ObservedObject as passing the view model further. If any body could explain me why in this case the state object worked it would be nice.

Thread 1: EXC_BAD_INSTRUCTION when fetching data

I get this Error -> Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) randomly. I don't quite understand when exactly it happens. Most of the times it is when the view refreshes. The Error appears at the line where group.leave() gets executed.
What am I trying to do:
I want to fetch albums with their image, name and songs that also have a name and image from my firebase database. I checked for the values and they're all right as far as I can tell. But when trying to show them it is random what shows. Sometimes everything is right, sometimes one album gets showed twice, sometimes only one album gets showed at all, sometimes one album has the songs of the other album.
My firebase database has albums stored as documents, each document has albumimage/name and 2 subcollections of "unlocked" with documents(user uid) that store "locked":Bool and "songs" with a document for each song that stores image/name
This is the function that fetches my albums with their songs:
let group = DispatchGroup()
#State var albums: [Album] = []
#State var albumSongs: [AlbumSong] = []
func fetchAlbums() {
FirebaseManager.shared.firestore.collection("albums").getDocuments { querySnapshot, error in
if let error = error {
print(error.localizedDescription)
return
}
guard let documents = querySnapshot?.documents else {
return
}
let uid = FirebaseManager.shared.auth.currentUser?.uid
documents.forEach { document in
let data = document.data()
let name = data["name"] as? String ?? ""
let artist = data["artist"] as? String ?? ""
let releaseDate = data["releaseDate"] as? Date ?? Date()
let price = data["price"] as? Int ?? 0
let albumImageUrl = data["albumImageUrl"] as? String ?? ""
let docID = document.documentID
FirebaseManager.shared.firestore.collection("albums").document(docID)
.collection("songs").getDocuments { querySnapshot, error in
if let error = error {
return
}
guard let documents = querySnapshot?.documents else {
return
}
self.albumSongs = documents.compactMap { document -> AlbumSong? in
do {
return try document.data(as: AlbumSong.self)
} catch {
return nil
}
}
group.leave()
}
FirebaseManager.shared.firestore.collection("albums").document(docID)
.collection("unlocked").document(uid ?? "").getDocument { docSnapshot, error in
if let error = error {
return
}
guard let document = docSnapshot?.data() else {
return
}
group.enter()
group.notify(queue: DispatchQueue.global()) {
if document["locked"] as! Bool == true {
self.albums.append(Album(name: name, artist: artist,
songs: albumSongs, releaseDate: releaseDate, price: price, albumImageUrl: albumImageUrl))
print("albums: ",albums)
}
}
}
}
}
}
I call my fetchAlbums() in my view .onAppear()
My AlbumSong:
struct AlbumSong: Identifiable, Codable {
#DocumentID var id: String? = UUID().uuidString
let title: String
let duration: TimeInterval
var image: String
let artist: String
let track: String
}
My Album:
struct Album: Identifiable, Codable {
#DocumentID var id: String? = UUID().uuidString
let name: String
let artist: String
let songs: [AlbumSong]
let releaseDate: Date
let price: Int
let albumImageUrl: String
}
I tried looking into how to fetch data from firebase with async function but I couldn't get my code to work and using dispatchGroup worked fine when I only have one album. I would appreciate answers explaining how this code would work with async, I really tried my best figuring it out by myself a long time. Also I would love to know what exactly is happening with DispatchGroup and why it works fine having one album but not with multiple ones.
I think you are over complicating something that is very simple with async await
First, your Models need some adjusting, it may be the source of some of your issues.
import Foundation
import FirebaseFirestore
import FirebaseFirestoreSwift
struct AlbumSong: Identifiable, Codable {
//No need to set a UUID `#DocumentID` provides an ID
#DocumentID var id: String?
let title: String
let duration: TimeInterval
var image: String
let artist: String
let track: String
}
struct Album: Identifiable, Codable {
//No need to set a UUID `#DocumentID` provides an ID
#DocumentID var id: String?
let name: String
let artist: String
//Change to var and make nil, the initial decoding will be blank
//If any of the other variables might be optional add the question mark
var songs: [AlbumSong]?
let releaseDate: Date
let price: Int
let albumImageUrl: String
}
Then you can create a service that does the heavy lifting with the Firestore.
struct NestedFirestoreService{
private let store : Firestore = .firestore()
let ALBUM_PATH = "albums"
let SONG_PATH = "songs"
///Retrieves Albums and Songs
func retrieveAlbums() async throws -> [Album] {
//Get the albums
var albums: [Album] = try await retrieve(path: ALBUM_PATH)
//Get the songs, **NOTE: leaving the array of songs instead of making a separate collection might work best.
for (idx, album) in albums.enumerated() {
if let id = album.id{
albums[idx].songs = try await retrieve(path: "\(ALBUM_PATH)/\(id)/\(SONG_PATH)")
}else{
print("\(album) :: has invalid id")
}
}
//Add another loop for `unlocked` here just like the one above.
return albums
}
///retrieves all the documents in the collection at the path
public func retrieve<FC : Identifiable & Codable>(path: String) async throws -> [FC]{
let querySnapshot = try await store.collection(path)
.getDocuments()
return try querySnapshot.documents.compactMap { document in
try document.data(as: FC.self)
}
}
}
Then you can implement it with just a few lines in your presentation layer.
import SwiftUI
#MainActor
class AlbumListViewModel: ObservableObject{
#Published var albums: [Album] = []
private let svc = NestedFirestoreService()
func loadAlbums() async throws{
albums = try await svc.retrieveAlbums()
}
}
struct AlbumListView: View {
#StateObject var vm: AlbumListViewModel = .init()
var body: some View {
List(vm.albums, id:\.id) { album in
DisclosureGroup(album.name) {
ForEach(album.songs ?? [], id:\.id){ song in
Text(song.title)
}
}
}.task {
do{
try await vm.loadAlbums()
}catch{
print(error)
}
}
}
}
struct AlbumListView_Previews: PreviewProvider {
static var previews: some View {
AlbumListView()
}
}
If you get any decoding errors make the variables optional by adding the question mark to the type like I did with the array.
Just use them in the correct order:
let group = DispatchGroup()
#State var albums: [Album] = []
#State var albumSongs: [AlbumSong] = []
func fetchAlbums() {
group.enter()
FirebaseManager.shared.firestore.collection("albums").getDocuments { querySnapshot, error in
if let error = error {
print(error.localizedDescription)
group.leave()
return
}
guard let documents = querySnapshot?.documents else {
group.leave()
return
}
let uid = FirebaseManager.shared.auth.currentUser?.uid
documents.forEach { document in
let data = document.data()
let name = data["name"] as? String ?? ""
let artist = data["artist"] as? String ?? ""
let releaseDate = data["releaseDate"] as? Date ?? Date()
let price = data["price"] as? Int ?? 0
let albumImageUrl = data["albumImageUrl"] as? String ?? ""
let docID = document.documentID
group.enter()
FirebaseManager.shared.firestore.collection("albums").document(docID)
.collection("songs").getDocuments { querySnapshot, error in
if let error = error {
group.leave()
return
}
guard let documents = querySnapshot?.documents else {
group.leave()
return
}
self.albumSongs = documents.compactMap { document -> AlbumSong? in
do {
group.leave()
return try document.data(as: AlbumSong.self)
} catch {
group.leave()
return nil
}
}
}
group.enter()
FirebaseManager.shared.firestore.collection("albums").document(docID)
.collection("unlocked").document(uid ?? "").getDocument { docSnapshot, error in
if let error = error {
group.leave()
return
}
guard let document = docSnapshot?.data() else {
group.leave()
return
}
if document["locked"] as! Bool == true {
self.albums.append(Album(name: name, artist: artist,
songs: albumSongs, releaseDate: releaseDate, price: price, albumImageUrl: albumImageUrl))
print("albums: ",albums)
}
group.leave()
}
}
group.leave()
}
group.notify(queue: DispatchQueue.global()) {
// do your stuff
}
}

I cant present my view with foreach in SwiftUI

I try to present a View-struct as often as there are items in an array. I want to use for-each, because I don't like the UIList view. Btw I'm using SwiftUI. I generate the array which I want to use from firebase-firestore.
Here is how I generate my array:
class ViewModellForItems: ObservableObject{
#Published var listItemsEnglisch = [MaterialItemClass]()
let myDataBase = Firestore.firestore()
let Ordner = Firestore.firestore().collection("Censored")
func updateData(){
Ordner.addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("Ordner wurde nicht gefunden")
return
}
self.listItemsMathe = documents.map { (queryDocumentSnapshot) -> MaterialItemClass in
let data = queryDocumentSnapshot.data()
let Name = data["Name"] as? String ?? ""
let beschreibung = data["beschreibung"] as? String ?? ""
let anzahlDesProduktes = data["anzahlDesProduktes"] as? Int ?? 0
let bildName = data["bildName"] as? String ?? ""
let hintergrundFarbe = data["hintergrundFarbe"] as? String ?? ""
let item = MaterialItemClass(Name: Name, beschreibung: beschreibung, anzahlDesProduktes: anzahlDesProduktes, bildName: bildName, hintergrundFarbe: hintergrundFarbe)
return item
}
}
}
}
Here is the Struct that I use in the ViewModellForItems Class:
struct MaterialItemClass {
var Name: String
var beschreibung: String
var anzahlDesProduktes: Int
var bildName: String
var hintergrundFarbe: String
}
And here is my ContendView.swift File:
struct ContendView: View {
#ObservedObject private var viewModel = ViewModellForItems()
var body: some View {
ForEach(0 ..< viewModel.listItemsEnglisch.count, id: \.self) {
Text(viewModel.listItemsEnglisch[$0].Name)
}.onAppear(){
self.viewModel.updateData()
}
Text("Debug")
}
}
I only get presented the Debug-Text... what am I doing wrong? And further; how can I present a whole View-Struct for each element I this array?
Just want to say, there's no fail of the firebase, because if I run almost the same code in a list view, everything is working fine...
Ok boys I got my mistake,
if I update my Array only in the .onAppear Methode of my for each block, it won't update, because the for-each block will never appear.
Thanks for your time
Boothosh

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?
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) }