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

