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

SwiftUI中MVVM架构下点击重置按钮评分未更新问题

SwiftUI MVVM架构下点击清除按钮后评分UI未同步更新问题

我正在使用SwiftUI测试MVVM架构,为商品列表实现了评分视图与清除按钮,期望点击清除按钮时重置所有商品的评分。点击按钮后已重置模型并更新State,但UI未同步更新,请问为何评分未被清除?

protocol ViewModelType: ObservableObject {
    associatedtype Input
    associatedtype State
    
    var state: State { get }
    func trigger(_ input: Input)
    
    var subscriptions: Set<AnyCancellable> { get }
}

class TestViewModel: ViewModelType {
        
    enum Input {
        case initialise
        case clearRating
    }
    
    enum State {
        case idle
        case testData(BagModel)
    }
    
    var subscriptions = Set<AnyCancellable>()
    
    @Published var state = State.idle
    var bag: BagModel?
    
    var products: [ProductModel] = [.init(id: 101, title: "Product 1", rating: 0),
                                    .init(id: 102, title: "Product 2", rating: 0)]
    
    func trigger(_ input: Input) {
        switch input {
        case .initialise:
            bag = BagModel(products: products)
            state = .testData(bag!)
            
        case .clearRating:
            products = [.init(id: 101, title: "Product 1", rating: 0),
                        .init(id: 102, title: "Product 2", rating: 0)]
            bag?.products = products
            state = .testData(bag!)
        }
    }
    
}

extension TestViewModel {
    struct BagModel {
        var products: [ProductModel]
    }
    
    struct ProductModel: Identifiable, Equatable {
        var id: Int
        var title: String
        var rating: Int
    }
}


struct TestView: View {
    
    @StateObject var viewModel = TestViewModel()
    
    var body: some View {
        VStack {
            switch viewModel.state {
            case .testData(let bags):
                VStack(alignment: .center, spacing: 20) {
                    ForEach(bags.products) { product in
                        Item(title: product.title, rating: product.rating)
                    }
                    
                    Button {
                        viewModel.trigger(.clearRating)
                    } label: {
                        Text("Clear Rating")
                    }

                }
                
            default:
                EmptyView()
            }
        }.onAppear {
            viewModel.trigger(.initialise)
        }
    }
}

extension TestView {
    struct Item: View {
        
        var title: String
        @State var rating: Int
                
        var body: some View {
            Group {
                Text(title)
                RatingStarView(rating: $rating)
            }
        }
    }
}

问题原因

  • 本地状态与ViewModel数据源脱节:Item视图用@State var rating存储评分,这是视图本地的独立状态,和ViewModel里的ProductModel只是初始值传递,没有双向绑定。ViewModel更新商品评分后,Item视图的本地@State不会自动同步。
  • State更新未触发视图刷新:BagModel是值类型,但你修改的是已存在的bag实例的products属性,之后把同一个bag实例赋值给state。SwiftUI的@Published仅在整个值发生变更时触发更新,这里bag的引用没有变,SwiftUI会认为State未变化,不会刷新视图。

解决方法

1. 让Item视图绑定到ViewModel的数据源

把Item的rating改为@Binding,实现和ViewModel数据的双向绑定:

extension TestView {
    struct Item: View {
        var title: String
        @Binding var rating: Int
        
        var body: some View {
            Group {
                Text(title)
                RatingStarView(rating: $rating)
            }
        }
    }
}

在TestView中传递绑定关系:

ForEach($bags.products) { $product in
    Item(title: product.title, rating: $product.rating)
}

2. 确保State更新触发视图刷新

修改clearRating的实现,直接创建新的BagModel实例,让state拿到全新的值:

case .clearRating:
    let resetProducts = [ProductModel(id: 101, title: "Product 1", rating: 0),
                         ProductModel(id: 102, title: "Product 2", rating: 0)]
    bag = BagModel(products: resetProducts)
    state = .testData(bag!)

3. 优化ViewModel数据管理(可选)

去掉冗余的products属性,直接通过bag管理商品数据:

class TestViewModel: ViewModelType {
    // 移除var products: [ProductModel]
    
    func trigger(_ input: Input) {
        switch input {
        case .initialise:
            let initialProducts = [ProductModel(id: 101, title: "Product 1", rating: 0),
                                   ProductModel(id: 102, title: "Product 2", rating: 0)]
            bag = BagModel(products: initialProducts)
            state = .testData(bag!)
            
        case .clearRating:
            let resetProducts = bag?.products.map {
                ProductModel(id: $0.id, title: $0.title, rating: 0)
            } ?? []
            bag = BagModel(products: resetProducts)
            state = .testData(bag!)
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 13:11:13