0

Struggling to get a simple example up and running in swiftui:

  1. Load default list view (working)
  2. click button that launches picker/filtering options (working)
  3. select options, then click button to dismiss and call function with selected options (call is working)
  4. display new list of objects returned from call (not working)

I'm stuck on #4 where the returned query isn't making it to the view. I suspect I'm creating a different instance when making the call in step #3 but it's not making sense to me where/how/why that matters.

I tried to simplify the code some, but it's still a bit, sorry for that.

Appreciate any help!

Main View with HStack and button to filter with:

import SwiftUI
import FirebaseFirestore

struct TestView: View {
    @ObservedObject var query = Query()
    @State var showMonPicker = false
    @State var monFilter = "filter"

    
    var body: some View {
        VStack {
            HStack(alignment: .center) {
                Text("Monday")
                Spacer()
                Button(action: {
                    self.showMonPicker.toggle()
                }, label: {
                    Text("\(monFilter)")
                })
            }
            .padding()
            
            ScrollView(.horizontal) {
                LazyHStack(spacing: 35) {
                    ForEach(query.queriedList) { menuItems in
                        MenuItemView(menuItem: menuItems)
                    }
                }
            }
        }
        .sheet(isPresented: $showMonPicker, onDismiss: {
            //optional function when picker dismissed
        }, content: {
            CuisineTypePicker(selectedCuisineType: $monFilter)
        })
    }
}

The Query() file that calls a base query with all results, and optional function to return specific results:

import Foundation
import FirebaseFirestore

class Query: ObservableObject {
    
    @Published var queriedList: [MenuItem] = []
    
    init() {
        baseQuery()
    }
    
    func baseQuery() {
      let queryRef = Firestore.firestore().collection("menuItems").limit(to: 50)
        
        queryRef
            .getDocuments() { (querySnapshot, err) in
            if let err = err {
                print("Error getting documents: \(err)")
            } else {
                self.queriedList = querySnapshot?.documents.compactMap { document in
                    try? document.data(as: MenuItem.self)
                    
                } ?? []
            }
        }
    }
    
    func filteredQuery(category: String?, glutenFree: Bool?) {
        var filtered = Firestore.firestore().collection("menuItems").limit(to: 50)

      // Sorting and Filtering Data
        if let category = category, !category.isEmpty {
          filtered = filtered.whereField("cuisineType", isEqualTo: category)
        }

        if let glutenFree = glutenFree, !glutenFree {
          filtered = filtered.whereField("glutenFree", isEqualTo: true)
        }

      filtered
            .getDocuments() { (querySnapshot, err) in
            if let err = err {
                print("Error getting documents: \(err)")
            } else {
                self.queriedList = querySnapshot?.documents.compactMap { document in
                    try? document.data(as: MenuItem.self);
                } ?? []
                print(self.queriedList.count)
            }
        }
    }
}

Picker view where I'm calling the filtered query:

import SwiftUI

struct CuisineTypePicker: View {
    
    @State private var cuisineTypes = ["filter", "American", "Chinese", "French"]
    @Environment(\.presentationMode) var presentationMode

    @Binding var selectedCuisineType: String
    @State var gfSelected = false
    
    let query = Query()
       
    var body: some View {
        VStack(alignment: .center) {
            //Buttons and formatting code removed to simplify.. 
            }
            .padding(.top)
            
            Picker("", selection: $selectedCuisineType) {
                ForEach(cuisineTypes, id: \.self) {
                    Text($0)
                }
            }
            Spacer()
            Button(action: {
                self.query.filteredQuery(category: selectedCuisineType, glutenFree: gfSelected)
                
                self.presentationMode.wrappedValue.dismiss()
                
            }, label: {
                Text( "apply filters")
            })               
        }
        .padding()
    }
}
2
  • I'm a little confused about the "apply filters" button. Inside, you execute filteredQuery on query, which is not annotated as @ObservedObject or @StateObject. Then, it dismisses the presentation mode, so that view will disappear, so that instance of Query will never get read. Perhaps the problems is that you mean to be using the same instance of Query between TestView and CuisineTypePicker, but you're not actually sharing it between the two? Commented Sep 28, 2021 at 20:43
  • @jnpdx Yes! that sounds like what I was thinking, but I couldn't get it straight, I see you posted a solution below, I'll dig in. Thanks for the help! Commented Sep 28, 2021 at 21:18

1 Answer 1

1

I suspect that the issue stems from the fact that you aren't sharing the same instance of Query between your TestView and your CuisineTypePicker. So, when you start a new Firebase query on the instance contained in CuisineTypePicker, the results are never reflected in the main view.

Here's an example of how to solve that (with the Firebase code replaced with some non-asynchronous sample code for now):

struct MenuItem : Identifiable {
    var id = UUID()
    var cuisineType : String
    var title : String
    var glutenFree : Bool
}

struct ContentView: View {
    @ObservedObject var query = Query()
    @State var showMonPicker = false
    @State var monFilter = "filter"
    
    var body: some View {
        VStack {
            HStack(alignment: .center) {
                Text("Monday")
                Spacer()
                Button(action: {
                    self.showMonPicker.toggle()
                }, label: {
                    Text("\(monFilter)")
                })
            }
            .padding()
            
            ScrollView(.horizontal) {
                LazyHStack(spacing: 35) {
                    ForEach(query.queriedList) { menuItem in
                        Text("\(menuItem.title) - \(menuItem.cuisineType)")
                    }
                }
            }
        }
        .sheet(isPresented: $showMonPicker, onDismiss: {
            //optional function when picker dismissed
        }, content: {
            CuisineTypePicker(query: query, selectedCuisineType: $monFilter)
        })
    }
}

class Query: ObservableObject {
    
    @Published var queriedList: [MenuItem] = []
    
    private let allItems: [MenuItem] = [.init(cuisineType: "American", title: "Hamburger", glutenFree: false),.init(cuisineType: "Chinese", title: "Fried Rice", glutenFree: true)]
    
    init() {
        baseQuery()
    }
    
    func baseQuery() {
        self.queriedList = allItems
    }
    
    func filteredQuery(category: String?, glutenFree: Bool?) {
        queriedList = allItems.filter({ item in
            if let category = category {
                return item.cuisineType == category
            } else {
                return true
            }
        }).filter({item in
            if let glutenFree = glutenFree {
                return item.glutenFree == glutenFree
            } else {
                return true
            }
        })
    }
}

struct CuisineTypePicker: View {
    @ObservedObject var query : Query
    @Binding var selectedCuisineType: String
    @State private var gfSelected = false
    
    private let cuisineTypes = ["filter", "American", "Chinese", "French"]
    @Environment(\.presentationMode) private var presentationMode
    
    var body: some View {
        VStack(alignment: .center) {
            //Buttons and formatting code removed to simplify..
        }
        .padding(.top)
        
        Picker("", selection: $selectedCuisineType) {
            ForEach(cuisineTypes, id: \.self) {
                Text($0)
            }
        }
        Spacer()
        Button(action: {
            self.query.filteredQuery(category: selectedCuisineType, glutenFree: gfSelected)
            self.presentationMode.wrappedValue.dismiss()
        }, label: {
            Text( "apply filters")
        })
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for taking the time, the shared instance of Query is exactly where I was going wrong. I had tried a few things but it hadn't clicked that i needed to pass the instance to the picker as an observed object. Much appreciated!

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.