Reputation: 1399
I currently have a function that's being called .onAppear that sets the state to a location name upon the user selecting it. That value is added to the Firestore query and gives results based on the selected location. My issue is that when the users select a different location, the function in .onAppear isn't called again. How would I go about structuring the code so that it runs the Firestore query again once State is changed? Below is the code from both the main view and the modal view.
struct ExploreView: View {
@State var selectedTab = "Explore"
@State var data: [RestaurantObject] = []
@State var newsData: [NewsObject] = []
@State var metro = "Orlando"
@State var didAppear = false
@State var appearCount = 0
@State var showingLocations = false
let db = Firestore.firestore()
var body: some View {
NavigationView {
ScrollView(.vertical, showsIndicators: false) {
VStack {
HStack {
Text("Explore ")
.font(.title)
.fontWeight(.bold)
+ Text(self.metro)
.font(.title)
.fontWeight(.bold)
Spacer()
Button(action: {
self.showingLocations.toggle()
}) {
Text ("Change")
.font(.callout)
}.sheet(isPresented: $showingLocations) {
LocationsModal(showingLocations: self.$showingLocations, metro: self.$metro)
}
}.padding(.horizontal)
.padding(.bottom, 30)
.onAppear(perform: getNews)
// Cuisine Slider
HStack {
Text("All cuisines")
.font(.title2)
.fontWeight(.bold)
Spacer()
NavigationLink (destination: CuisinesView()) {
Text("View all")
}
}.padding(.horizontal)
.padding(.bottom, 20)
ScrollView (.horizontal, showsIndicators: false) {
HStack(spacing: 12.0) {
CuisineTile(image: "cuisine_breakfast", name: "Brunch")
CuisineTile(image: "cuisine_bbq", name: "BBQ")
CuisineTile(image: "cuisine_brazilian", name: "Brazilian")
CuisineTile(image: "cuisine_caribbean", name: "Caribbean")
CuisineTile(image: "cuisine_cuban", name: "Cuban")
CuisineTile(image: "cuisine_mexican", name: "Mexican")
CuisineTile(image: "cuisine_seafood", name: "Seafood")
CuisineTile(image: "cuisine_soulfood", name: "Soul Food")
.padding(.trailing, 31)
}.offset(x: 16)
}
// Featured Section
VStack {
HStack {
Text("Featured Eats")
.font(.title2)
.fontWeight(.bold)
.foregroundColor(Color.white)
Spacer()
}.padding(.horizontal)
.padding(.top)
HStack {
Text("Join us every month as we highlight business owners with uplifting and inspiring stories.")
.foregroundColor(Color.white)
.font(.subheadline)
Spacer()
}.padding(.horizontal)
.padding(.vertical, 5)
ZStack {
VStack {
HStack {
Image("placeholder_feature")
// .frame(width: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/, height: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
.resizable()
.scaledToFill()
}.padding(.horizontal)
}
VStack {
Spacer()
HStack {
Text ("The story behind Papa Llama. Orlando's newest Peruvian restaurant.")
.font(.body)
.foregroundColor(Color.white)
.padding()
Spacer()
}.background(Color("CharcoalGray"))
.cornerRadius(10, corners: [.bottomLeft, .bottomRight])
.padding(.horizontal)
}
}
HStack {
Button(action: /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Action@*/{}/*@END_MENU_TOKEN@*/) {
Text ("Read More")
.font(.subheadline)
.fontWeight(.medium)
.padding(.horizontal, 16)
.padding(.vertical, 10)
.background(Color.black)
.foregroundColor(.white)
.overlay (
RoundedRectangle(cornerRadius: 6) .stroke(Color.white, lineWidth: 2)
)
}.padding(.top, 12)
.padding(.horizontal)
Spacer()
}
.padding(.bottom)
}.background(Color.black)
.padding(.vertical)
VStack {
HStack {
Text(self.metro).font(.title2)
.fontWeight(.bold) + Text(" Eats")
.font(.title2)
.fontWeight(.bold)
Spacer()
NavigationLink(destination: LocationRestaurants(location: self.metro)) {
Text("View all")
}
}.padding(.horizontal)
.padding(.top, 20)
VStack {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 12.0) {
ForEach((self.data), id: \.self.restaurantID) { item in
NavigationLink(destination: RestaurantDetail(name: item.restaurantName, image: item.restaurantImage, address: item.restaurantAddress)) {
ExploreTile(image: item.restaurantImage, name: item.restaurantName, category: item.restaurantCategory)
}.buttonStyle(PlainButtonStyle())
}
}.offset(x: 16)
}
}.padding(.bottom, 30)
}
HStack {
Text("News Bites")
.font(.title2)
.fontWeight(.bold)
Spacer()
}.padding(.horizontal)
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach((self.newsData), id: \.self.newsID) { item in
NavigationLink(destination: NewsDetailView(url: item.newsURL)) {
NewsArticleTile(title: item.newsTitle, photo: item.newsPhoto, author: item.newsAuthor, source: item.newsSource, url: item.newsURL).padding(.trailing, 10)
}.buttonStyle(PlainButtonStyle())
}
}
}.offset(x: 16)
.padding(.bottom, 100)
}
}
.onChange(of: self.metro, perform: { _ in
getRestaurants()
print("Metro value changed to \(self.metro)")
})
.onAppear(perform: getRestaurants)
.navigationBarTitle("")
.toolbar {
ToolbarItem(placement: .principal) {
Image("ue_logo")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
}
}
}
}
func getRestaurants() {
if didAppear == false {
appearCount += 1
self.data.removeAll()
self.db.collection("businesses").whereField("metro", isEqualTo: self.metro).limit(to: 4).addSnapshotListener ( {(querySnapshot, err) in
if let err = err {
print("Error getting documents \(err)")
} else {
for document in querySnapshot!.documents {
let id = document.documentID
let name = document.get("name") as! String
let image = document.get("photo") as? Array ?? [""]
let category = document.get("category") as? Array ?? [""]
let address = document.get("address1") as! String
let city = document.get("city") as! String
let state = document.get("state") as! String
let zipcode = document.get("zip") as! String
let owned = document.get("owned") as! String
let phone = document.get("phone") as! String
let metro = document.get("metro") as! String
let url = document.get("website") as! String
let delivery = document.get("delivery") as! Bool
let sitdown = document.get("sitdown") as! Bool
let takeout = document.get("takeout") as! Bool
let outdoor = document.get("outdoor") as! Bool
self.data.append(RestaurantObject(id: id, name: name, image: image[0], category: category[0], address: address, city: city, state: state, zipcode: zipcode, owned: owned, phone: phone, metro: metro, url: url, delivery: delivery, sitdown: sitdown, takeout: takeout, outdoor: outdoor))
}
}
})
}
didAppear = true
}
func getNews() {
if didAppear == false {
appearCount += 1
self.newsData.removeAll()
self.db.collection("news").limit(to: 3).getDocuments() {(querySnapshot, err) in
if let err = err {
print("Error getting articles \(err)")
} else {
for document in querySnapshot!.documents {
let id = document.documentID
let title = document.get("title") as! String
let photo = document.get("photo") as! String
let author = document.get("author") as! String
let source = document.get("source") as! String
let url = document.get("url") as! String
self.newsData.append(NewsObject(id: id, title: title, photo: photo, author: author, source: source, url: url))
}
}
}
}
}
}
struct ExploreView_Previews: PreviewProvider {
static var previews: some View {
ExploreView()
}
}
extension View {
func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
clipShape( RoundedCorner(radius: radius, corners: corners) )
}
}
struct RoundedCorner: Shape {
var radius: CGFloat = .infinity
var corners: UIRectCorner = .allCorners
func path(in rect: CGRect) -> Path {
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
return Path(path.cgPath)
}
}
class RestaurantObject: ObservableObject {
@Published var restaurantID: String
@Published var restaurantName: String
@Published var restaurantImage: String
@Published var restaurantCategory: String
@Published var restaurantAddress: String
@Published var restaurantCity: String
@Published var restaurantState: String
@Published var restaurantZip: String
@Published var restaurantOwned: String
@Published var restaurantPhone: String
@Published var restaurantMetro: String
@Published var restaurantURL: String
@Published var restaurantDelivery: Bool
@Published var restaurantSitdown: Bool
@Published var restaurantTakeout: Bool
@Published var restaurantOutdoor: Bool
init(id: String, name: String, image: String, category: String, address: String, city: String, state: String, zipcode: String, owned: String, phone: String, metro: String, url: String, delivery: Bool, sitdown: Bool, takeout: Bool, outdoor: Bool) {
restaurantID = id
restaurantName = name
restaurantImage = image
restaurantCategory = category
restaurantAddress = address
restaurantCity = city
restaurantState = state
restaurantZip = zipcode
restaurantOwned = owned
restaurantPhone = phone
restaurantMetro = metro
restaurantURL = url
restaurantDelivery = delivery
restaurantSitdown = sitdown
restaurantTakeout = takeout
restaurantOutdoor = outdoor
}
}
class NewsObject: ObservableObject {
@Published var newsID: String
@Published var newsTitle: String
@Published var newsPhoto: String
@Published var newsAuthor: String
@Published var newsSource: String
@Published var newsURL: String
init(id: String, title: String, photo: String, author: String, source: String, url: String) {
newsID = id
newsTitle = title
newsPhoto = photo
newsAuthor = author
newsSource = source
newsURL = url
}
}
Modal View
import SwiftUI
import Firebase
import SDWebImageSwiftUI
struct LocationsModal: View {
@State var data: [LocationsObject] = []
@Binding var showingLocations: Bool
@Binding var metro: String
let db = Firestore.firestore()
var body: some View {
NavigationView {
ScrollView(/*@START_MENU_TOKEN@*/.vertical/*@END_MENU_TOKEN@*/, showsIndicators: false) {
HStack {
Text("Choose a location")
.font(.title)
.fontWeight(.bold)
Spacer()
}.padding(.horizontal)
.padding(.bottom, 30)
LazyVStack {
ForEach((self.data), id: \.self.locationID) { item in
Button(action: {
self.showingLocations = false; self.metro = item.locationName}) {
HStack {
WebImage(url: URL(string: item.locationImage))
.onSuccess { image, data, cacheType in
}
.resizable()
.placeholder(Image(systemName: "photo"))
.placeholder {
Rectangle().foregroundColor(.gray)
}
.indicator(.activity) // Activity Indicator
.transition(.fade(duration: 0.5))
.scaledToFill()
.frame(width: 80, height: 80, alignment: .center)
.clipped()
Text(item.locationName)
Spacer()
}
}
Spacer()
}
.buttonStyle(PlainButtonStyle())
.padding(.horizontal)
}
}.onAppear {
self.getLocations()
}
.navigationBarTitle("")
.toolbar {
ToolbarItem(placement: .principal) {
Image("ue_logo")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
}
}
}
}
func getLocations() {
self.data.removeAll()
self.db.collection("metros").order(by: "name").getDocuments() {(querySnapshot, err) in
if let err = err {
print("Error getting documents \(err)")
} else {
for document in querySnapshot!.documents {
let id = document.documentID
let name = document.get("name") as! String
let image = document.get("image") as! String
let lat = document.get("lat") as! Double
let long = document.get("long") as! Double
self.data.append(LocationsObject(id: id, name: name, image: image, lat: lat, long: long))
}
}
}
}
}
Upvotes: 1
Views: 1432
Reputation: 1399
Thank you Asperi for leading me down the right path. The issue in my code was that I needed to reset the value of didAppear to "false" in order to get my function to run again. Setting it back to false and then calling the getRestaurants function updated my query as expected.
Upvotes: 0
Reputation: 257719
It is not clear on which of state in your code exactly you want to do same as on .onAppear
, but the approach is as below:
.onAppear(perform: getNews)
//.onReceive(Just(_your_state_property_)) { _ in // SwiftUI 1.0 + import Combine
.onChange(of: _your_state_property_) { _ in // SwiftUI 2.0
self.getNews()
}
Upvotes: 2