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

SwiftUI卡牌发牌优化:解决卡牌随数量增加尺寸变化问题

解决SwiftUI卡牌发牌时尺寸动态变化的问题

问题场景

点击牌堆需发12张卡牌,最终呈现3行4列布局,但发牌过程中,系统会先按每行3张排列,随后切换为每行4张,导致卡牌尺寸动态变化,视觉效果怪异。需要实现卡牌尺寸固定,不随发牌数量变化。

问题根源

当前AspectVGrid的widthThatFits方法会根据当前已发卡牌的数量动态计算列数:

  • 当卡牌数量较少时(比如1-3张),列数会从1逐步增加到3
  • 直到卡牌数量达到4张及以上,列数才会调整为2,最终到12张时列数变为4
    每次列数变化都会重新计算卡牌宽度,导致尺寸跳变。

解决方案

修改AspectVGrid,支持传入固定列数,强制按指定列数布局,从而固定卡牌尺寸。

修改后的AspectVGrid代码

import SwiftUI

struct AspectVGrid<Item, ItemView>: View where ItemView: View, Item: Identifiable {
    var items: [Item]
    var aspectRatio: CGFloat
    var fixedColumnCount: Int? // 新增固定列数参数
    var content: (Item) -> ItemView
    
    init(items: [Item], aspectRatio: CGFloat, fixedColumnCount: Int? = nil, @ViewBuilder content: @escaping (Item) -> ItemView) {
        self.items = items
        self.aspectRatio = aspectRatio
        self.fixedColumnCount = fixedColumnCount
        self.content = content
    }
    
    var body: some View {
        GeometryReader { geometry in
            ScrollView {
                VStack {
                    let computedWidth: CGFloat = widthThatFits(in: geometry.size)
                    let width: CGFloat = computedWidth > 65 ? computedWidth : 65
                    LazyVGrid(columns: [adaptiveGridItem(width: width)], spacing: 0) {
                        ForEach(items) { item in
                            content(item).aspectRatio(aspectRatio, contentMode: .fit)
                        }
                    }
                    Spacer(minLength: 0)
                }
            }
        }
    }
    
    private func adaptiveGridItem(width: CGFloat) -> GridItem {
        var gridItem = GridItem(.adaptive(minimum: width))
        gridItem.spacing = 0
        return gridItem
    }
    
    private func widthThatFits(in size: CGSize) -> CGFloat {
        if let columnCount = fixedColumnCount {
            // 如果指定了固定列数,直接用总宽度除以列数计算卡牌宽度
            return floor(size.width / CGFloat(columnCount))
        } else {
            // 保留原有动态计算逻辑
            var columnCount = 1
            var rowCount = items.count
            repeat {
                let itemWidth = size.width / CGFloat(columnCount)
                let itemHeight = itemWidth / aspectRatio
                if CGFloat(rowCount) * itemHeight < size.height {
                    break
                }
                columnCount += 1
                rowCount = (items.count + (columnCount - 1)) / columnCount
            } while columnCount < items.count
            if columnCount > items.count {
                columnCount = items.count
            }
            return floor(size.width / CGFloat(columnCount))
        }
    }
}

修改ContentView中的调用

在gameBody里调用AspectVGrid时,传入fixedColumnCount: 4,强制按4列布局:

var gameBody: some View {
    AspectVGrid(items: viewModel.playingCards, aspectRatio: 2/3, fixedColumnCount: 4) { card in
        CardView(viewModel: viewModel, card: card)
            .matchedGeometryEffect(id: card.id, in: dealingNamespace)
            .padding(4)
            .onTapGesture {
                withAnimation {
                    viewModel.choose(card)
                }
            }
            .foregroundColor(viewModel.borderColor(for: card))
        }
    .padding(.horizontal)
}

效果说明

修改后,无论当前已发卡牌数量是多少,都会按4列布局计算卡牌宽度,尺寸保持固定。发牌过程中只会新增卡牌,不会出现布局列数和卡牌尺寸的跳变,视觉效果更流畅。

内容的提问来源于stack exchange,提问作者Real Analysis Noob

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 22:39:25