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
相关产品推荐
相关产品推荐

