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

如何在SwiftUI中高效实现可动态编辑的交互式2D画布

在SwiftUI中实现带连线的交互式TextView画布

完全可以在SwiftUI里高效实现这类需求,下面是基础框架的搭建思路和示例代码:

核心实现思路

  • 用可观察对象管理动态数据:包括每个TextView的位置、内容、唯一ID,以及连接TextView的连线数据
  • 用Canvas组件绘制连线:相比叠加Shape,Canvas在处理大量线条时性能更优
  • 结合DragGesture实现TextView的拖拽调整,操作数据驱动视图更新
  • 通过修改数据数组实现TextView的动态增删,SwiftUI会自动响应数据变化刷新界面

示例代码

1. 定义数据模型

import SwiftUI

// 单个TextView的数据模型
struct TextViewData: Identifiable {
    let id = UUID()
    var text: String
    var position: CGPoint
}

// 连线的数据模型(关联两个TextView的ID)
struct Connection: Identifiable {
    let id = UUID()
    let fromId: UUID
    let toId: UUID
}

// 全局状态管理器
class CanvasState: ObservableObject {
    @Published var textViews: [TextViewData] = []
    @Published var connections: [Connection] = []
    
    // 添加新的TextView
    func addTextView(at position: CGPoint) {
        textViews.append(TextViewData(text: "新文本", position: position))
    }
    
    // 删除指定TextView及关联的连线
    func removeTextView(_ view: TextViewData) {
        textViews.removeAll { $0.id == view.id }
        connections.removeAll { $0.fromId == view.id || $0.toId == view.id }
    }
    
    // 添加连线(示例:连接最后两个TextView,实际可根据交互调整)
    func addConnection() {
        guard textViews.count >= 2 else { return }
        let lastTwo = textViews.suffix(2)
        connections.append(Connection(fromId: lastTwo.first!.id, toId: lastTwo.last!.id))
    }
}

2. 主画布视图

struct CanvasView: View {
    @StateObject private var state = CanvasState()
    @State private var selectedTextView: TextViewData?
    
    var body: some View {
        ZStack {
            // 绘制连线
            Canvas { context, size in
                for connection in state.connections {
                    guard let fromView = state.textViews.first(where: { $0.id == connection.fromId }),
                          let toView = state.textViews.first(where: { $0.id == connection.toId }) else { continue }
                    
                    // 计算TextView的中心位置作为连线端点
                    let fromCenter = fromView.position
                    let toCenter = toView.position
                    
                    context.stroke(Path { path in
                        path.move(to: fromCenter)
                        path.addLine(to: toCenter)
                    }, with: .color(.blue), lineWidth: 2)
                }
            }
            .gesture(TapGesture().onEnded { location in
                // 点击空白处添加新TextView
                state.addTextView(at: location)
            })
            
            // 渲染所有TextView
            ForEach($state.textViews) { $textView in
                Text(textView.text)
                    .padding()
                    .background(.white)
                    .cornerRadius(8)
                    .shadow(radius: 2)
                    .position(textView.position)
                    .gesture(
                        DragGesture()
                            .onChanged { value in
                                textView.position = value.location
                            }
                    )
                    .contextMenu {
                        Button("删除") {
                            state.removeTextView(textView)
                        }
                        Button("连接到最后一个") {
                            state.addConnection()
                        }
                    }
            }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(.gray.opacity(0.1))
        .navigationTitle("交互式画布")
    }
}

// 预览
struct CanvasView_Previews: PreviewProvider {
    static var previews: some View {
        NavigationStack {
            CanvasView()
        }
    }
}

关键说明

  • 性能优化:使用Canvas绘制所有连线,避免了多个Shape视图的叠加开销,适合大量连线场景
  • 动态交互:点击空白区域添加TextView,长按TextView调出菜单可删除或添加连线,拖动TextView会实时更新连线位置
  • 扩展性:可以扩展数据模型,比如给TextView添加尺寸、颜色属性,给连线添加曲线样式、箭头等,或者实现更复杂的连线交互(比如拖拽端点创建连线)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 08:20:55