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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 08:57:20