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

SwiftUI如何获取列表内视图中心坐标并在选中的两个视图间绘制连线

实现方案

你可以通过 SwiftUI 提供的 PreferenceKey 机制获取动态生成视图的全局坐标,再通过自定义路径绘制两点连线,完整适配动态新增/删除元素的场景,具体实现如下:

实现逻辑

  • 定义坐标存储用的 PreferenceKey,分别记录目标视图、源视图的索引与对应中心坐标
  • 给 ForEach 生成的每个目标/源元素添加坐标读取逻辑,上报自身中心坐标到全局
  • 在 ZStack 底层添加连线绘制层,触发连接操作时直接读取选中索引对应的坐标绘制连线

完整修改后代码

import SwiftUI

// 坐标存储数据结构
struct NodePoint: Equatable {
    var index: Int
    var center: CGPoint
}

// 存储目标视图坐标的PreferenceKey
struct TargetPointsPreference: PreferenceKey {
    static var defaultValue: [NodePoint] = []
    static func reduce(value: inout [NodePoint], nextValue: () -> [NodePoint]) {
        value.append(contentsOf: nextValue())
    }
}

// 存储源视图坐标的PreferenceKey
struct SourcePointsPreference: PreferenceKey {
    static var defaultValue: [NodePoint] = []
    static func reduce(value: inout [NodePoint], nextValue: () -> [NodePoint]) {
        value.append(contentsOf: nextValue())
    }
}

struct ContentView: View {
    @State private var selectedTarget: Int = 0
    @State private var selectedSource: Int = 3
    @State private var isConnected = false
    // 全局存储所有目标、源视图的索引对应坐标
    @State private var targetPoints: [Int: CGPoint] = [:]
    @State private var sourcePoints: [Int: CGPoint] = [:]
    
    var body: some View {
        ZStack {
            // 连线绘制层,放在最底层
            if isConnected, let targetCenter = targetPoints[selectedTarget], let sourceCenter = sourcePoints[selectedSource] {
                Path { path in
                    path.move(to: targetCenter)
                    path.addLine(to: sourceCenter)
                }
                .stroke(Color.blue, lineWidth: 2)
            }
            
            VStack {
                HStack {
                    ForEach(0..<6) { index in
                        Text("Target \(index)")
                            .padding()
                            .foregroundColor(.white)
                            .background(selectedTarget == index ? .red : .black)
                            .onTapGesture {
                                selectedTarget = index
                            }
                            // 读取当前目标视图的中心坐标
                            .background {
                                GeometryReader { geo in
                                    Color.clear
                                        .preference(key: TargetPointsPreference.self, value: [
                                            NodePoint(index: index, center: geo.frame(in: .global).center)
                                        ])
                                }
                            }
                    }
                }
                // 接收上报的所有目标视图坐标
                .onPreferenceChange(TargetPointsPreference.self) { value in
                    targetPoints = value.reduce(into: [:]) { $0[$1.index] = $1.center }
                }
                
                List {
                    EmptyView()
                }
                .frame(width: 400, height: 400, alignment: .center)
                .border(.black, width: 2)
        
                HStack {
                    ForEach(0..<6) { index in
                        Text("Source \(index)")
                            .padding()
                            .foregroundColor(.white)
                            .background(selectedSource == index ? .orange : .black)
                            .onTapGesture {
                                selectedSource = index
                            }
                            // 读取当前源视图的中心坐标
                            .background {
                                GeometryReader { geo in
                                    Color.clear
                                        .preference(key: SourcePointsPreference.self, value: [
                                            NodePoint(index: index, center: geo.frame(in: .global).center)
                                        ])
                                }
                            }
                    }
                }
                // 接收上报的所有源视图坐标
                .onPreferenceChange(SourcePointsPreference.self) { value in
                    sourcePoints = value.reduce(into: [:]) { $0[$1.index] = $1.center }
                }
                
                Button(isConnected ? "CUT" : "CONNECT") {
                    isConnected.toggle()
                }
                .padding()
            }
        }
    }
}

动态增减元素适配说明

如果需要支持动态新增/删除目标/源元素,只需把固定的 0..<6 替换为 @State 修饰的动态数组即可,坐标会随着元素的增减自动更新,无需额外修改坐标读取逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 00:45:05