如何在Swift中自定义GridStack视图实现元素周期表布局及交互
交互式元素周期表实现方案
核心改造点
- 完善
shouldDisplayAtCoord函数实现行级列数规则控制 - 新增元素数据模型与全局状态管理,实现点击数据传递
- 单元格渲染逻辑绑定真实元素数据,替换固定氢元素内容
1. 先定义数据模型与全局状态
import SwiftUI // 元素数据模型 struct Element: Identifiable { let id = UUID() let number: Int // 原子序数 let symbol: String // 元素符号 let name: String // 元素名称 let row: Int // 所在行 let column: Int // 所在列 } // 全局状态管理,用于传递选中的元素 class ElementViewModel: ObservableObject { @Published var selectedElement: Element? // 所有元素数据,可根据实际周期表数据补全 let allElements: [Element] = [ Element(number: 1, symbol: "H", name: "氢", row: 0, column: 0), Element(number: 2, symbol: "He", name: "氦", row: 0, column: 1), Element(number: 3, symbol: "Li", name: "锂", row: 1, column: 0), Element(number: 4, symbol: "Be", name: "铍", row: 1, column: 1), // 剩余元素按位置补全即可 ] // 根据行、列获取对应元素 func getElement(at row: Int, column: Int) -> Element? { allElements.first { $0.row == row && $0.column == column } } }
2. 改造GridStack组件
struct GridStack<Content: View>: View { let rows: Int let columns: Int let content: (Int, Int) -> Content @EnvironmentObject var vm: ElementViewModel // 按规则判断单元格是否显示 func shouldDisplayAtCoord(row: Int, column: Int) -> Bool { switch row { case 0: return column < 2 // 第1行仅显示2列 case 1, 2: return column < 8 // 第2、3行仅显示8列 case 3,4,5,6: return true // 第4-7行显示18列 case 7,8: return column < 15 // 第8、9行显示15列 default: return false } } var body: some View { VStack(spacing: 4) { ForEach(0 ..< rows, id: \.self) { row in HStack(spacing: 4) { ForEach(0 ..< columns, id: \.self) { column in if shouldDisplayAtCoord(row: row, column: column) { ZStack{ RoundedRectangle(cornerRadius: 5) .fill(Color.brown) .frame(width: 40, height: 50) Image("RadicalDeepscale30").opacity(0.4) content(row, column) } .onTapGesture { // 点击传递当前元素到全局 vm.selectedElement = vm.getElement(at: row, column: column) } } else { // 不显示的位置用透明视图占位,保持布局对齐 Color.clear .frame(width: 40, height: 50) } } } } } } init(rows: Int, columns: Int, @ViewBuilder content: @escaping (Int, Int) -> Content) { self.rows = rows self.columns = columns self.content = content } }
3. 改造DETEView实现数据渲染
struct DETEView: View { @StateObject private var vm = ElementViewModel() var body: some View { VStack(alignment: .center, spacing: 20) { Text("DART Edge 元素周期表") .font(.largeTitle) .bold() .padding(.top, 20) .shadow(radius: 3) // 可在这里添加选中元素展示区域 if let selected = vm.selectedElement { VStack { Text("当前选中:\(selected.name)(\(selected.symbol))") Text("原子序数:\(selected.number)") } .font(.headline) .padding() .background(Color.gray.opacity(0.1)) .cornerRadius(8) } GridStack(rows: 9, columns: 18) { row, col in if let element = vm.getElement(at: row, column: col) { VStack(alignment: .leading, spacing: 4){ Text("\(element.number)") .font(.system(size: 9)) .frame(maxWidth: .infinity, alignment: .leading) VStack(spacing: 2){ Text(element.symbol) .font(.system(size: 12)) .bold() Text(element.name) .font(.system(size: 7)) } .frame(maxWidth: .infinity) } .padding(4) } } .environmentObject(vm) } .frame(width: 950, height: 670, alignment: .top) .padding(.top, 20) } }
使用说明
- 把
ElementViewModel里的allElements数组按实际元素周期表的行、列位置补全所有元素数据即可 - 选中的元素会存在
ElementViewModel.selectedElement中,可在任意子视图通过@EnvironmentObject读取使用 - 布局间距、单元格大小可根据实际需求调整
VStack/HStack的spacing参数和RoundedRectangle的frame参数
内容的提问来源于stack exchange,提问作者user16933914
相关产品推荐
相关产品推荐

