SwiftUI中@Published状态变量为何无法更新UI?
问题描述
我将AppState类作为环境变量在整个App中共享,用于在所有视图间共享核心数据:
class AppState: ObservableObject { @Published var user: User? @Published var matches = Matches( status: .NOT_LOADED, data: [], performedFullLoad: false, canFetchMore: false ) }
AppState在ContentView中实例化:
struct ContentView: View { @StateObject private var appState = AppState() var body: some View { Group { if appState.user == nil { // 未登录时展示AuthView AuthView() } else { // 登录后展示MatchesView MatchesView() } } .environmentObject(appState) } }
用户登录后,调用Matches.loadMatchesList()方法更新AppState.matches中的数据:
final class Matches: ObservableObject { @Published var status: LoadingStatus @Published var data: [Match] @Published var performedFullLoad: Bool @Published var canFetchMore: Bool init(status: LoadingStatus, data: [Match], performedFullLoad: Bool, canFetchMore: Bool) { self.status = status self.data = data self.performedFullLoad = performedFullLoad self.canFetchMore = canFetchMore } func loadMatchesList(userId: String) async { do { // 获取匹配数据并解析为parsedMatches变量 ... // 存储匹配数据 DispatchQueue.main.async { self.status = .LOADED self.data = parsedMatches self.performedFullLoad = true self.canFetchMore = decodedData.count > numOfMatchesToLoad print("\(parsedMatches.count) matches loaded into state variable") // 运行时打印"2" } } catch let error { ... } } }
最后在MatchesView中展示加载的匹配列表,问题出现:print语句确认已有2条匹配数据存入数组,但渲染的List却显示0条数据。
struct MatchesView: View { @EnvironmentObject var appState: AppState var body: some View { NavigationView { List(appState.matches.data) { match in NavigationLink { MatchDetail(match: match) } label: { MatchRow(match: match) } } } } }
已确认MatchesView访问的是唯一正确的AppState实例,点击按钮打印appState.matches.data.count会输出"2",但UI始终不更新显示这2条匹配数据。为何环境对象符合ObservableObject协议且data属性已用@Published修饰,UI却不更新?
原因分析
AppState中的matches属性用@Published修饰,但@Published只会在整个Matches对象被替换时才会向视图发送变更通知。- 当调用
loadMatchesList()更新matches内部的data、status等属性时,AppState的matches对象本身并没有被替换,因此AppState不会发出任何变更通知,视图也就不会重新渲染。 - 虽然
Matches自身是ObservableObject且内部属性用@Published修饰,但视图监听的是AppState这个环境对象,而非直接监听Matches,所以Matches内部的变更无法触发视图更新。
解决方案
提供两种可行的解决方式:
方案一:将Matches的属性直接整合到AppState中
移除独立的Matches类,把相关属性直接放到AppState里,这样所有@Published属性的变更都会直接触发视图更新:
class AppState: ObservableObject { @Published var user: User? @Published var matchesStatus: LoadingStatus = .NOT_LOADED @Published var matchesData: [Match] = [] @Published var matchesPerformedFullLoad: Bool = false @Published var matchesCanFetchMore: Bool = false func loadMatchesList(userId: String) async { do { // 获取匹配数据并解析为parsedMatches变量 ... DispatchQueue.main.async { self.matchesStatus = .LOADED self.matchesData = parsedMatches self.matchesPerformedFullLoad = true self.matchesCanFetchMore = decodedData.count > numOfMatchesToLoad } } catch let error { ... } } }
然后更新MatchesView的引用:
List(appState.matchesData) { match in // ... 原有代码 }
方案二:让AppState监听Matches的内部变更
保留Matches类,通过Combine监听Matches的objectWillChange事件,当Matches内部属性变更时,手动触发AppState的objectWillChange通知:
import Combine class AppState: ObservableObject { @Published var user: User? var matches = Matches( status: .NOT_LOADED, data: [], performedFullLoad: false, canFetchMore: false ) private var cancellables = Set<AnyCancellable>() init() { // 监听Matches的变更,转发给AppState的订阅者 matches.objectWillChange .sink { [weak self] _ in self?.objectWillChange.send() } .store(in: &cancellables) } }
这样当Matches内部的data等属性更新时,AppState会同步发出变更通知,视图就会重新渲染并展示最新数据。
内容的提问来源于stack exchange,提问作者Sam
相关产品推荐
相关产品推荐

