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
相关产品推荐
相关产品推荐

