SwiftUI使用Preference Key实现等尺寸字母网格失败,求问题排查
问题分析与修复方案
你的代码存在4个核心问题,导致字母网格布局不符合预期:
1. PreferenceKey的reduce逻辑错误
当前WidthPreference的reduce方法仅取第一个非nil的宽度值,但我们需要的是所有单元格文本中的最大宽度来统一尺寸,这样才能保证所有方格大小一致。
修复代码:
struct WidthPreference: PreferenceKey { static let defaultValue: CGFloat = 0 static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value = max(value, nextValue()) } }
2. LetterView中frame与padding的顺序错误
你把padding放在frame外面,导致实际单元格尺寸是frame宽度 + padding,但我们测量的是Text的原始宽度(未包含padding),最终会让方格大小不一致。需要把padding移到frame内部。
3. 每个LetterView独立监听偏好,导致宽度不一致
每个LetterView单独监听WidthPreference,会出现部分单元格先拿到宽度就先渲染,后续单元格拿到的宽度可能不同。正确做法是在AlphabetGrid层级统一监听最大宽度,再传递给所有LetterView。
4. HStack未设置均匀分布
HStack默认根据内容宽度排列,没有让每个LetterView占满行内可用空间,需要给每个LetterView添加frame(maxWidth: .infinity)来保证每行单元格均匀分配宽度。
完整修复后的代码
1. PreferenceKey与View扩展修改
struct WidthPreference: PreferenceKey { static let defaultValue: CGFloat = 0 static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value = max(value, nextValue()) } } extension View { func sizePreference() -> some View { background(GeometryReader { proxy in Color.clear .preference(key: WidthPreference.self, value: proxy.size.width) }) } }
2. AlphabetGrid修改(统一监听宽度并传递)
struct AlphabetGrid: View { @EnvironmentObject var theModel: MyModel @State private var maxCellWidth: CGFloat = 0 var spacing: CGFloat = 8 var body: some View { guard let allLetters = theModel.allLetters else { return EmptyView() } let theKeyboard = [ allLetters.filter { $0.keyboardRow == 0 }, allLetters.filter { $0.keyboardRow == 1 }, allLetters.filter { $0.keyboardRow == 2 } ] VStack(spacing: spacing) { ForEach(theKeyboard, id: \.self) { keyboardRow in HStack(spacing: spacing) { ForEach(keyboardRow, id: \.self) { keyboardLetter in LetterView( theIdx: keyboardLetter.letterStorePosition, borderColour: .blue, cellWidth: maxCellWidth, spacing: spacing ) .frame(maxWidth: .infinity) } } } } .onPreferenceChange(WidthPreference.self) { newValue in maxCellWidth = newValue } } }
3. LetterView修改(接收统一宽度,调整padding顺序)
struct LetterView: View { @EnvironmentObject var theModel: MyModel var theIdx: Int var borderColour: Color var cellWidth: CGFloat var spacing: CGFloat = 8 var body: some View { Text(String(theModel.allLetters?[theIdx].letterStoreChar ?? "*")) .sizePreference() .frame(width: cellWidth, height: cellWidth, alignment: .center) .padding(spacing) .overlay( RoundedRectangle(cornerRadius: 8) .stroke(borderColour, lineWidth: 1) ) } }
4. ContentView与MyModel代码保持不变
修改后,所有方格会基于文本的最大宽度设置统一尺寸,HStack会让每个单元格均匀分配行内空间,最终实现等尺寸字母网格效果。
内容的提问来源于stack exchange,提问作者coco
相关产品推荐
相关产品推荐

