首页 > 解决方案 > SwiftUI - 状态更改后的运行功能

问题描述

我目前有一个名为 .onAppear 的函数,它在用户选择它时将状态设置为位置名称。该值将添加到 Firestore 查询并根据所选位置提供结果。我的问题是,当用户选择不同的位置时,不会再次调用 .onAppear 中的函数。我将如何构建代码以便在状态更改后再次运行 Firestore 查询?下面是主视图和模态视图的代码。

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

模态视图

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

}

标签: swiftui

解决方案


目前尚不清楚您希望在代码中的哪个状态与 on 完全相同.onAppear,但方法如下:

.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()
}

推荐阅读