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

SwiftUI异步网络请求后@State值不更新,无法更新DetailView数据

问题分析

你的核心问题在于:League是值类型(struct),当你在ViewModel里更新leagues数组中某个League的seasons时,DetailView里的@State var league是独立的副本,不会自动同步ViewModel中数组里的最新数据。另外,ChangeButton切换联赛时,虽然通过Binding更新了DetailView的league,但这个新的league同样是ViewModel数组里的副本,后续ViewModel更新该league的seasons时,DetailView的副本还是不会同步。

解决方案

我们需要让DetailView直接依赖ViewModel中的数据源,而不是维护自己的@State副本。具体修改如下:

1. 修改DetailView,用联赛ID追踪选中项

移除@State var league,改用@State var selectedLeagueId,通过ID从ViewModel的数组中实时获取最新的联赛数据:

struct DetailView: View {
    @EnvironmentObject var vm: LeagueViewModel
    @State var selectedLeagueId: String
    
    // 实时从ViewModel获取当前选中的联赛
    private var currentLeague: League? {
        vm.leagues.first(where: { $0.id == selectedLeagueId })
    }
    
    var body: some View {
        VStack {
            if let league = currentLeague {
                if let unwrappedSeasons = league.seasons {
                    List {
                        ForEach(unwrappedSeasons, id: \.year) { season in
                            Text(season.displayName)
                        }
                    }
                } else {
                    ProgressView()
                }
            } else {
                Text("联赛不存在")
            }
        }
        .onAppear {
            Task {
                try await vm.loadSeasons(forLeague: selectedLeagueId)
            }
        }
        .navigationBarTitle(Text(currentLeague?.name ?? "联赛详情"), displayMode: .inline)
        .toolbar {
            ToolbarItem(placement: .navigationBarTrailing) {
                ChangeButton(selectedLeagueId: $selectedLeagueId)
            }
        }
    }
}

2. 修改ChangeButton,绑定联赛ID

不再绑定League实例,而是绑定ID,切换时直接更新ID,让DetailView自动获取最新数据:

struct ChangeButton: View {
    @EnvironmentObject var vm: LeagueViewModel
    @Binding var selectedLeagueId: String
    
    var body: some View {
        Menu {
            ForEach(vm.leagues) { league in
                Button {
                    self.selectedLeagueId = league.id
                } label: {
                    Text(league.name)
                }
            }
        } label: {
            Image(systemName: "calendar")
        }
    }
}

3. 更新ContentView的导航链接传递ID

把原来传递League实例改成传递ID:

struct ContentView: View {
    @StateObject var vm = LeagueViewModel()
    
    var body: some View {
        NavigationView {
            VStack {
                if vm.leagues.isEmpty {
                    ProgressView()
                } else {
                    List {
                        ForEach(vm.leagues) { league in
                            NavigationLink(destination: DetailView(selectedLeagueId: league.id)) {
                                Text(league.name)
                            }
                        }
                    }
                }
            }
            .navigationBarTitle(Text("联赛列表"), displayMode: .large)
        }
        .environmentObject(vm)
    }
}

额外优化:确保ViewModel更新触发视图刷新

因为League是struct,修改数组中元素的内部属性时,数组本身的引用不会变化,@Published可能无法触发刷新。修改loadSeasons方法,通过替换整个元素来确保通知发出:

func loadSeasons(forLeague id: String) async throws {
    let (data, _) = try await URLSession.shared.data(from: URL(string: "https://api-football-standings.azharimm.site/leagues/\(id)/seasons")!)
    guard let seasons = try? JSONDecoder().decode(SeasonResponse.self, from: data) else {
        throw URLError(.cannotParseResponse)
    }
    await MainActor.run {
        if let responsedLeagueIndex = leagues.firstIndex(where: { $0.id == id }),
           let unwrappedSeasons = seasons.data?.seasons {
            // 创建新的League实例替换原数组中的元素
            var updatedLeague = leagues[responsedLeagueIndex]
            updatedLeague.seasons = unwrappedSeasons
            leagues[responsedLeagueIndex] = updatedLeague
            print(unwrappedSeasons)
        }
    }
}
原方案失效原因
  • @State var league是DetailView内部的状态,是ViewModel中league的值拷贝,ViewModel更新数组里的league时,这个拷贝不会自动同步。
  • 用@Binding传递拷贝给ChangeButton,切换时只是把另一个league的拷贝赋值给它,依然和ViewModel的数据源脱节。

改用ID追踪后,DetailView始终从ViewModel的leagues数组中获取数据,ViewModel的数据更新会自动触发视图刷新,完美解决异步更新后的同步问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 09:33:20