SwiftUI - 状态更改后的运行功能
Posted
技术标签:
【中文标题】SwiftUI - 状态更改后的运行功能【英文标题】:SwiftUI - Run function after state change 【发布时间】:2020-11-02 01:20:12 【问题描述】:我目前有一个名为 .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))
【问题讨论】:
【参考方案1】:目前尚不清楚您的代码中的哪个状态与.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()
【讨论】:
对不起,我正在寻找观察“metro”状态的变化并让它重新运行“self.getRestaurants()”的函数。 .onAppear(perform: getRestaurants) .onChange(of: self.metro) _ in self.getRestaurants() 我添加了 .onChange 修饰符并在其中包含了一个打印语句。更改“metro”的值后会触发 print 语句,但 getRestaurants 函数似乎不会再次触发。我更新了上面的代码以反映我的尝试。【参考方案2】:感谢 Asperi 带领我走上正确的道路。我的代码中的问题是我需要将 didAppear 的值重置为“false”才能让我的函数再次运行。将其设置回 false,然后调用 getRestaurants 函数按预期更新了我的查询。
【讨论】:
以上是关于SwiftUI - 状态更改后的运行功能的主要内容,如果未能解决你的问题,请参考以下文章