SwiftUI卡牌游戏临时视图状态管理优化咨询
Swift卡牌游戏选中卡片状态管理优化方案
现有问题分析
你之前的实现要么把选中状态放在View的@State里,导致ViewModel需要通过inout参数操作外部状态,耦合度高;要么状态管理逻辑分散,代码冗余。最优方案是把选中状态完全移到ViewModel中,利用@Published实现状态驱动UI更新,同时遵循MVVM的职责分离原则。
优化后的代码结构
1. 重构ViewModel(集中管理状态)
把selectedCardIds改为ViewModel的@Published属性,所有选中逻辑都在VM内部处理,不再依赖外部参数:
class GameViewModel: ObservableObject { @Published private(set) var cards: [Card] // 游戏卡片数组,假设从模型加载 @Published private(set) var selectedCardIds: Set<Int> = [] private let model: GameModel // 你的底层游戏模型 private let maxSelectionCount = 3 // 最多选中3张卡片 // 初始化方法,加载卡片等逻辑 init(model: GameModel) { self.model = model self.cards = model.loadCards() // 示例:从模型加载卡片 } func choose(card: Card) { // 已匹配的卡片不能被选中 guard card.state != .matched else { return } // 选中数量达到上限时,清空现有选中状态 if selectedCardIds.count >= maxSelectionCount { selectedCardIds.removeAll() } // 切换当前卡片的选中状态 toggleCardSelection(card) // 当恰好选中3张时,触发模型的匹配逻辑 if selectedCardIds.count == maxSelectionCount { model.tryToMatchCards(withIds: selectedCardIds) model.cleanUpTheTable() // 匹配完成后清空选中状态(可根据游戏规则调整) selectedCardIds.removeAll() // 更新卡片状态(从模型同步到VM的cards数组) cards = model.loadCards() } } private func toggleCardSelection(_ card: Card) { if selectedCardIds.contains(card.id) { selectedCardIds.remove(card.id) } else { selectedCardIds.insert(card.id) } } // 卡片数据结构 struct Card: Identifiable { let id: Int var state: CardState // 其他卡片属性(如图片、数值等) } enum CardState { case unmatched, matched } }
2. 简化View层代码
View只需要绑定ViewModel的状态,无需自己管理selectedCardIds,代码更简洁:
struct GameView: View { @ObservedObject var game: GameViewModel var body: some View { LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 3)) { ForEach(game.cards) { card in CardView(card: card, isSelected: game.selectedCardIds.contains(card.id)) .onTapGesture { game.choose(card: card) } } } } } struct CardView: View { let card: GameViewModel.Card let isSelected: Bool var body: some View { // 示例卡片UI,根据isSelected切换样式 RoundedRectangle(cornerRadius: 12) .fill(isSelected ? .blue.opacity(0.6) : .white) .border(isSelected ? .blue : .gray, width: 2) .padding(4) // 添加卡片内容(如图片、文字等) .overlay(Text("\(card.id)").font(.title)) } }
优化优势
- 状态内聚:选中状态完全由ViewModel掌控,逻辑更集中,便于维护和测试。
- 低耦合:View和ViewModel之间通过状态绑定通信,不再依赖
inout参数传递状态。 - 自动UI更新:
@Published属性会自动通知View刷新,无需手动处理状态变化的UI同步。 - 逻辑清晰:把选中规则、匹配触发等游戏逻辑都放在ViewModel中,View只负责渲染和用户交互。
可选优化点
- 如果卡片状态(如
matched)会动态变化,确保ViewModel的cards数组是@Published的,这样状态变化时UI能自动更新。 - 可以给ViewModel添加计算属性,比如
currentSelectionCount,方便View快速获取当前选中数量。 - 匹配失败时,可以保留选中状态让玩家重新选择,只需调整
choose方法中清空选中状态的时机。
内容的提问来源于stack exchange,提问作者Panic
相关产品推荐
相关产品推荐

