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 - 状态更改后的运行功能的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI 如何根据获取的数据更改视图的背景颜色?

在 Swift/SwiftUI 中更改十进制数的字体大小

Swift之深入解析SwiftUI属性包装器如何处理结构体

数据加载到视图SwiftUI后执行功能

更改状态栏颜色 SwiftUI 没有 UIHosting

动画不适用于 SwiftUI 视图状态更改