SwiftUI macOS应用:右键ContextMenu获取点击位置识别线条
针对你的频谱展示场景需求,这里提供两种可行的解决方案,分别适配不同的使用场景:
方案一:为每条线条绑定独立的可点击区域(推荐)
问题根源:你之前用ForEach绘制单条线条时,contentShape(Rectangle())会把整个视图的矩形作为点击区域,导致所有线条的点击区域重叠,最后一条的区域覆盖了前面所有线条,因此点击任何位置都只会触发最后一条的菜单。
解决思路:为每条线条设置独立的垂直窄条作为可点击区域,宽度设为相邻线条间距的一半,确保点击区域不重叠且覆盖线条两侧的区域,这样点击X方向任意位置都会落到对应线条的可点击范围内。
ForEach(Array(legends.enumerated()), id: \.offset) { offset, _ in // 计算当前线条的X坐标 let lineX = CGFloat(offset) * spacing * pixelPerHz // 可点击区域宽度:相邻线条间距的一半,确保覆盖线条两侧的点击范围 let tapAreaWidth = spacing * pixelPerHz / 2 // 可点击区域的起始X:当前线条X减去半宽,让区域居中于线条 let tapAreaX = lineX - tapAreaWidth / 2 Path { path in path.move(to: CGPoint(x: lineX, y: 0)) path.addLine(to: CGPoint(x: lineX, y: height)) } .stroke(color, lineWidth: 1) // 设置仅当前线条对应的垂直窄条为可点击区域 .contentShape( Rectangle() .frame(width: tapAreaWidth, height: height) .offset(x: tapAreaX, y: 0) ) .contextMenu { Button("Do It") { print("触发第 \(offset + 1) 条线的操作,X坐标:\(lineX)") } } }
方案优势
- 完全遵循SwiftUI声明式风格,逻辑清晰
- 点击区域精准对应线条,无歧义
- 无需额外手势处理,直接利用原生
contextMenu特性
方案二:全局点击+计算对应线条(支持任意位置点击)
如果需要支持点击任意X位置(包括线条之间的空白区域),自动匹配到最近的线条,可以通过SpatialTapGesture获取点击位置,计算对应线条索引后手动唤起ContextMenu。
@State private var showContextMenu = false @State private var selectedLineIndex = 0 @State private var selectedLineX: CGFloat = 0 var body: some View { ZStack { // 绘制所有竖线 Path { path in var xPosition: CGFloat = (-low.truncatingRemainder(dividingBy: params.spacing)) * pixelPerHz repeat { path.move(to: CGPoint(x: xPosition, y: 0)) path.addLine(to: CGPoint(x: xPosition, y: height)) xPosition += pixelPerHz * params.spacing } while xPosition < width } .stroke(color, lineWidth: 1) // 透明覆盖层,接收所有右键点击 Color.clear .contentShape(Rectangle()) .gesture( SpatialTapGesture(count: 1, coordinateSpace: .local) .triggeredBy(.secondary) // 响应macOS右键点击 .onEnded { event in let tapX = event.location.x let spacingPx = pixelPerHz * params.spacing // 计算点击位置对应的最近线条索引(四舍五入) selectedLineIndex = Int((tapX + spacingPx / 2) / spacingPx) selectedLineX = CGFloat(selectedLineIndex) * spacingPx showContextMenu = true } ) .contextMenu(isPresented: $showContextMenu) { Button("Do It") { print("触发第 \(selectedLineIndex + 1) 条线的操作,X坐标:\(selectedLineX)") } } } }
方案优势
- 支持任意位置点击,自动匹配最近的线条
- 无需为每条线条单独绑定菜单,适合线条数量极多的场景
内容的提问来源于stack exchange,提问作者DougPA
相关产品推荐
相关产品推荐

