You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中带Published结构体数组的ObservableObject为何不通知视图更新?

问题:Firestore更新后SwiftUI视图未刷新

核心问题

我有一个关联Firebase的ViewModel,里面用@Published修饰了一个结构体数组,但从数据库获取更新后,依赖这个Published变量的视图没有收到更新通知。

代码

ViewModel

@MainActor class RestaurantsViewModel: ObservableObject {
    @Published var restaurants: [Restaurant]
    private var db = Firestore.firestore()

    init() {
        self.restaurants = []
    
        db.collection("Restaurants").addSnapshotListener(includeMetadataChanges: true) { [self] (querySnapshot, error) in
            guard let documents = querySnapshot?.documents else {
                print("No documents")
                return
            }
        
            let newRestaurants = documents.map { queryDocumentSnapshot -> Restaurant in
                    /* 解析文档数据的逻辑 */
                }

            self.restaurants = newRestaurants
        }
    }
}

Restaurant结构体

struct Restaurant: Identifiable {
    let id: String
    let name: String
    let location: CLLocation
    let votes: Int

    init(id: String, name: String, location: CLLocation, votes: Int) {
        self.id = id
        self.name = name
        self.location = location
        self.votes = votes
    }
}

预期会更新的视图

struct RestaurantsView: View {
    // 已确认此处ViewModel实例与接收数据库更新的实例为同一个(通过打印内存地址验证)
    @EnvironmentObject var restarauntViewModel: RestaurantsViewModel

    var body: some View {
        VStack(content: {
            Text("")
            
            VStack(content: {
                ScrollView {
                    VStack(content: {
                        ForEach(restarauntViewModel.restaurants, id: \.id) { restaurant in
                            RestaurantView(restaurant: restaurant)
                        }
                    })
                }
            })
        })
    }
}
struct RestaurantView: View {
    @State var restaurant: Restaurant
    
    var body: some View {
        HStack(content: {
            NameView(name: restaurant.name)
            VoteView(voteCount: restaurant.votes)
        })
        .onTapGesture {
            withAnimation {
                /* 点击逻辑 */
            }
        }
    }
}

疑问

有没有什么我漏掉的明显原因,导致数据库更新时RestaurantsView里的ForEach不更新?


解决方案

以下是几个可能的原因及对应的修复方式:

1. 主线程更新缺失

Firestore的快照回调默认在后台线程执行,即便ViewModel标注了@MainActor,闭包本身仍会在后台运行。直接在闭包内修改@Published变量,会导致更新通知无法正确触发SwiftUI视图刷新。

修复方法:将更新restaurants的代码切换到主线程:

db.collection("Restaurants").addSnapshotListener(includeMetadataChanges: true) { [self] (querySnapshot, error) in
    guard let documents = querySnapshot?.documents else {
        print("No documents")
        return
    }

    let newRestaurants = documents.map { queryDocumentSnapshot -> Restaurant in
            /* 解析逻辑 */
        }

    // 切换至主线程更新
    DispatchQueue.main.async {
        self.restaurants = newRestaurants
    }
}

或者用Task绑定主线程执行:

Task { @MainActor in
    self.restaurants = newRestaurants
}

2. RestaurantView的@State误用

RestaurantView中用@State修饰restaurant是错误的——@State用于存储视图内部的可变状态,而非接收外部传入的数据源。外部传入的restaurant更新时,@State会保留初始值,无法响应外部变化。

修复方法:将@State改为普通的let(若无需在视图内修改)或@Binding(若需修改restaurant):

struct RestaurantView: View {
    let restaurant: Restaurant 
    
    var body: some View {
        HStack(content: {
            NameView(name: restaurant.name)
            VoteView(voteCount: restaurant.votes)
        })
        .onTapGesture {
            withAnimation {
                /* 点击逻辑 */
            }
        }
    }
}

3. Restaurant结构体的Equatable缺失(可选验证)

SwiftUI的ForEach依赖元素的可识别性和相等性判断是否需要更新视图。虽然已实现Identifiable,但如果Restaurant属性更新后,SwiftUI无法正确判断数组元素变化,也可能导致不刷新。

修复方法:让Restaurant遵循Equatable协议:

struct Restaurant: Identifiable, Equatable {
    let id: String
    let name: String
    let location: CLLocation
    let votes: Int

    init(id: String, name: String, location: CLLocation, votes: Int) {
        self.id = id
        self.name = name
        self.location = location
        self.votes = votes
    }
    
    // 手动实现Equatable,因CLLocation默认不遵循该协议
    static func == (lhs: Restaurant, rhs: Restaurant) -> Bool {
        lhs.id == rhs.id &&
        lhs.name == rhs.name &&
        lhs.location.coordinate.latitude == rhs.location.coordinate.latitude &&
        lhs.location.coordinate.longitude == rhs.location.coordinate.longitude &&
        lhs.votes == rhs.votes
    }
}

这样ForEach能更精准地检测元素变化,触发对应RestaurantView的更新。

4. 拼写错误检查

注意到RestaurantsView中的@EnvironmentObject变量名为restarauntViewModel(少了一个字母u,正确应为restaurantViewModel),虽已验证内存地址一致,但仍建议统一拼写,避免潜在引用错误。


内容的提问来源于stack exchange,提问作者Brinkster

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 22:23:25