Jason Tremain
Jason Tremain

Reputation: 1399

SwiftUI - Run function after state change

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

Answers (2)

Jason Tremain
Jason Tremain

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

Asperi
Asperi

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

Related Questions