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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 16:50:55