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

如何在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)
    }
}

使用说明

  1. 把ElementViewModel里的allElements数组按实际元素周期表的行、列位置补全所有元素数据即可
  2. 选中的元素会存在ElementViewModel.selectedElement中,可在任意子视图通过@EnvironmentObject读取使用
  3. 布局间距、单元格大小可根据实际需求调整VStack/HStack的spacing参数和RoundedRectangle的frame参数

内容的提问来源于stack exchange,提问作者user16933914

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 05:54:07