SwiftUI中检测SwiftData模型更新的最优方案是什么?
SwiftData对象更新后SwiftUI视图不刷新的解决方案
问题根源
你当前的实现里,用@State单独存储balance,还通过isLoading只在视图初始化时同步一次模型值。但SwiftData的@Query会自动监听模型变化并触发视图更新,可你的balance是独立的状态变量,不会跟着模型更新同步,导致下拉刷新后视图没法即时显示新值。
解决方案
1. 直接绑定SwiftData模型属性,移除冗余状态
没必要用@State单独存balance,直接用@Query返回的模型对象属性就行。当SwiftData模型更新时,@Query会自动触发视图刷新,模型属性也会同步更新。
修改AssetView:
struct AssetView: View { @Environment(\.modelContext) private var context let assetID: PersistentIdentifier // 移除冗余的@State变量 @Query private var assets: [Asset] init(assetID: PersistentIdentifier) { self.assetID = assetID _assets = Query(filter: #Predicate<Asset> { $0.persistentModelID == assetID }) } var body: some View { if let asset = assets.first { ScrollView { HStack(alignment: .center) { Text(asset.name!) // 直接使用模型的balance属性 Text(": $\(asset.balance, specifier: "%.2f")") } } .refreshable { Task { if let asset = assets.first { let random = Double.random(in: 0...2) let currentBalance = asset.balance asset.balance *= random try! context.save() } } } // 移除原来的task代码 } else { Text("No assets available") } } }
2. 用计算属性替代手动维护合计值
Portfolio和Account里的balance是手动维护的,容易出现同步错误。改成计算属性,让合计值自动根据子项计算,SwiftData会自动监听子项变化,触发视图更新。
修改Portfolio模型:
@Model final class Portfolio { @Attribute(.unique) var id = UUID().uuidString var name: String? // 改成计算属性,自动计算所有账户的余额总和 var balance: Double { accounts.reduce(0) { $0 + $1.balance } } @Relationship(deleteRule: .cascade) var accounts: [Account] = [] init(name: String, accounts: [Account]) { self.name = name self.accounts = accounts } // 移除手动更新balance的add方法,只需要管理数组 func add(account: Account) { guard !accounts.contains(account) else { return } accounts.append(account) } func add(accounts: [Account]) { accounts.forEach(add(account:)) } }
修改Account模型:
@Model final class Account { @Attribute(.unique) var id = UUID().uuidString var name: String? // 改成计算属性,自动计算所有资产的余额总和 var balance: Double { assets.reduce(0) { $0 + $1.balance } } @Relationship(inverse: \Portfolio.accounts) var portfolio: Portfolio? @Relationship(deleteRule: .cascade) var assets: [Asset] = [] init(name: String, assets: [Asset]) { self.name = name self.assets = assets } // 移除手动更新balance的add方法 func add(asset: Asset) { guard !assets.contains(asset) else { return } assets.append(asset) } func add(assets: [Asset]) { assets.forEach(add(asset:)) } }
3. 优化视图中的合计显示
PortfolioView和AccountView的合计文本,直接用模型的计算属性,不需要单独的@State存储:
修改PortfolioView:
struct PortfolioView: View { let portfolioID: PersistentIdentifier @Query private var portfolios: [Portfolio] init(portfolioID: PersistentIdentifier) { self.portfolioID = portfolioID _portfolios = Query(filter: #Predicate<Portfolio> { $0.persistentModelID == portfolioID }) } var body: some View { if let portfolio = portfolios.first { List { Section { ForEach(portfolio.accounts) { account in NavigationLink(destination: AccountView(accountID: account.persistentModelID)) { HStack(alignment: .center) { Text(account.name!) Text(": $\(account.balance, specifier: "%.2f")") } } } } header: { Text("Accounts") } footer: { // 直接使用portfolio的balance计算属性 Text("Total: \(portfolio.balance, format: .currency(code: Locale.current.currency?.identifier ?? "USD"))") } } // 移除原来的task代码 } } }
AccountView做同样修改,移除@State的balance和isLoading,直接使用account.balance。
效果验证
修改后,下拉刷新AssetView时,资产余额会即时更新,同时父视图的账户合计、组合合计也会自动同步更新,不需要返回再进入。
内容的提问来源于stack exchange,提问作者RXP
相关产品推荐
相关产品推荐

