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

使用LazyVGrid/LazyVStack时按钮图标切换异常问题求助

问题原因分析

这个问题的核心是LazyVGrid的视图复用机制加上Favorites类的状态变更没有正确通知视图刷新导致的:

  1. Lazy容器的复用特性:LazyVGrid只会渲染当前可见区域的视图,当你滚动到不可见区域时,之前的视图会被复用给新出现的单元格。如果视图的状态没有正确绑定到可观察的数据源,复用的时候就会显示旧的状态。
  2. Favorites的状态通知缺失:你的Favorites类里,用来判断游戏是否收藏的games数组不是@Published属性。虽然你手动调用了objectWillChange.send(),但这个调用时机不够可靠,而且@Published会自动处理状态变更的通知,确保所有依赖的视图都能收到更新。
解决方案

1. 修复Favorites类的状态通知

把games数组改成@Published属性,这样当它的内容变化时,会自动触发ObservableObject的变更通知,所有依赖它的视图都会刷新。同时可以去掉手动的objectWillChange.send()调用,因为@Published会帮我们处理:

class Favorites: ObservableObject {
    //The fetched games by id are stored here.
    @Published var favGames: [Game] = []
    @Published var isLoading = false
    // 👇 把games改成@Published
    @Published var games: [String]
    var gameService = Store.shared
    let userDefaults = UserDefaults.standard
    
    // the key we're using to read/write in UserDefaults
    private let saveKey = "Favorites"
    
    init() {
        // load our saved data
        self.games = userDefaults.stringArray(forKey: saveKey) ?? []
    }
    
    // returns true if set contains the game
    func contains(_ game: Game) -> Bool {
        return games.contains(String(game.id))
    }
    
    // adds game to set, updates all views, and saves the change
    func add(_ game: Game) {
        // 👇 移除手动的objectWillChange.send()
        games.append(String(game.id))
        save()
    }
    
    // removes the game from set, updates all views, and saves the change
    func remove(_ game: Game) {
        // 👇 移除手动的objectWillChange.send()
        games.remove(object: String(game.id))
        save()
    }
    
    // ... 其他方法保持不变
}

2. 确保GameCard正确响应状态变更

因为你已经在GameCard里使用了@EnvironmentObject var favorites: Favorites,只要favorites.games是@Published的,当它变化时,GameCard的body会自动重新计算,图标就会正确切换了。

额外优化:避免LazyVGrid复用导致的状态残留

如果上面的修改后还有偶尔的问题,可以给GameCard加上一个明确的id绑定,确保LazyVGrid能正确识别每个单元格的状态:

在GameListView的LazyVGrid里的GameCard添加id:

GameCard(game: game)
    .id(game.id) // 👇 添加这个,确保每个单元格有唯一标识
    .onTapGesture {
        self.selectedGame = game
        print(self.selectedGame!)
    }

这样LazyVGrid会把每个GameCard和对应的游戏ID绑定,复用的时候会优先匹配ID,减少状态残留的可能。

为什么普通VStack没问题?

普通VStack会一次性渲染所有视图,每个视图都是独立的,不会被复用,所以即使状态通知有问题,点击的时候视图也会因为按钮动作触发的局部刷新而更新。但Lazy容器的复用机制放大了状态通知缺失的问题,导致旧状态被复用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 08:47:33