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

SwiftUI实现Excel风格视图的单元格可编辑功能求助

解决SwiftUI类Excel视图的单元格编辑问题

要实现单个单元格的编辑功能,核心是维护单元格数据状态并处理TextField的交互逻辑,以下是具体改造方案:

1. 定义单元格数据存储

首先需要一个二维数组保存每个单元格的内容,替换原有的静态(row, col)文本:

@State private var cellValues: [[String]] = Array(repeating: Array(repeating: "", count: 20), count: 30)

初始化时填充默认标识,保持初始显示效果与原有一致:

init() {
    _cellValues = State(initialValue: Array(0..<30).map { row in
        Array(0..<20).map { col in
            "(\(row), \(col))"
        }
    })
}

2. 跟踪编辑状态

添加状态变量记录当前编辑的单元格位置,以及临时绑定值处理实时输入:

@State private var editingRow: Int?
@State private var editingCol: Int?
@State private var tempText: String = ""

3. 改造单元格视图

将原Text替换为可交互的TextField,处理点击切换编辑、焦点管理与内容保存:

// 改造后的单元格代码
Group {
    if editingRow == row && editingCol == col {
        TextField("", text: $tempText)
            .frame(width: 70, height: 50)
            .border(Color.blue)
            .background(Color.yellow.opacity(0.2))
            .onSubmit {
                cellValues[row][col] = tempText
                editingRow = nil
                editingCol = nil
            }
            .onDisappear {
                cellValues[row][col] = tempText
            }
    } else {
        Text(cellValues[row][col])
            .frame(width: 70, height: 50)
            .border(Color.blue)
            .onTapGesture {
                tempText = cellValues[row][col]
                editingRow = row
                editingCol = col
            }
    }
}
.id("\(row)_\(col)")

通过条件判断切换文本显示或编辑框,点击文本激活编辑,提交/失去焦点时保存内容。

完整改造后的代码

struct ContentView: View {
    
    let columns = 20
    let rows = 30
    
    @State private var offset = CGPoint.zero
    // 存储单元格数据
    @State private var cellValues: [[String]]
    // 编辑状态跟踪
    @State private var editingRow: Int?
    @State private var editingCol: Int?
    @State private var tempText: String = ""
    
    init() {
        // 初始化单元格默认值
        _cellValues = State(initialValue: Array(0..<rows).map { row in
            Array(0..<columns).map { col in
                "(\(row), \(col))"
            }
        })
    }
    
    var body: some View {
        
        HStack(alignment: .top, spacing: 0) {
            
            VStack(alignment: .leading, spacing: 0) {
                // empty corner
                Color.clear.frame(width: 70, height: 50)
                ScrollView([.vertical]) {
                    rowsHeader
                        .offset(y: offset.y)
                }
                .disabled(true)
            }
            VStack(alignment: .leading, spacing: 0) {
                ScrollView([.horizontal]) {
                    colsHeader
                        .offset(x: offset.x)
                }
                .disabled(true)

                table
                    .coordinateSpace(name: "scroll")
            }
        }
        .padding()
    }
    
    var colsHeader: some View {
        HStack(alignment: .top, spacing: 0) {
            ForEach(0..<columns) { col in
                Text("COL \(col)")
                    .foregroundColor(.secondary)
                    .font(.caption)
                    .frame(width: 70, height: 50)
                    .border(Color.blue)
            }
        }
    }
    
    var rowsHeader: some View {
        VStack(alignment: .leading, spacing: 0) {
            ForEach(0..<rows) { row in
                Text("ROW \(row)")
                    .foregroundColor(.secondary)
                    .font(.caption)
                    .frame(width: 70, height: 50)
                    .border(Color.blue)
            }
        }
    }
    
    var table: some View {
        ScrollView([.vertical, .horizontal]) {
            VStack(alignment: .leading, spacing: 0) {
                ForEach(0..<rows) { row in
                    HStack(alignment: .top, spacing: 0) {
                        ForEach(0..<columns) { col in
                            // 改造后的单元格
                            Group {
                                if editingRow == row && editingCol == col {
                                    TextField("", text: $tempText)
                                        .frame(width: 70, height: 50)
                                        .border(Color.blue)
                                        .background(Color.yellow.opacity(0.2))
                                        .onSubmit {
                                            cellValues[row][col] = tempText
                                            editingRow = nil
                                            editingCol = nil
                                        }
                                        .onDisappear {
                                            cellValues[row][col] = tempText
                                        }
                                } else {
                                    Text(cellValues[row][col])
                                        .frame(width: 70, height: 50)
                                        .border(Color.blue)
                                        .onTapGesture {
                                            tempText = cellValues[row][col]
                                            editingRow = row
                                            editingCol = col
                                        }
                                }
                            }
                            .id("\(row)_\(col)")
                        }
                    }
                }
            }
            .background( GeometryReader { geo in
                Color.clear
                    .preference(key: ViewOffsetKey.self, value: geo.frame(in: .named("scroll")).origin)
            })
            .onPreferenceChange(ViewOffsetKey.self) { value in
                print("offset >> \(value)")
                offset = value
            }
        }
    }
    
}

struct ViewOffsetKey: PreferenceKey {
    typealias Value = CGPoint
    static var defaultValue = CGPoint.zero
    static func reduce(value: inout Value, nextValue: () -> Value) {
        value.x += nextValue().x
        value.y += nextValue().y
    }
}

关键说明

  • 二维数组cellValues持久化存储单元格内容,确保修改后数据不丢失
  • editingRow/editingCol控制编辑状态,同一时间仅允许编辑一个单元格
  • 点击文本单元格激活TextField,临时变量tempText处理实时输入,提交/失焦时同步回数据源
  • 编辑状态添加背景色区分,提升用户交互体验

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 10:40:44