问题描述
我正在使用SwiftUI开发macOS应用程序,该应用程序应该能够在屏幕上创建,排列和删除几何形状。使用上下文菜单可以很好地创建和拖动形状。
import SwiftUI
class Canvas: ObservableObject {
@Published var nodes: [Node] = []
func addNode(position: CGPoint) -> Void {
nodes.append(Node(id: UUID(),position: position))
}
}
struct CanvasView: View {
@Observedobject var canvas = Canvas()
var body: some View {
ZStack {
Color(red: 0.9,green: 0.9,blue: 0.8)
.contextMenu {
Button( action: {
self.canvas.addNode(position: CGPoint(x: 400,y: 400))
} )
{ Text("Add Node ...") }
}
ForEach(canvas.nodes) {node in
NodeView(node: node)
}
}
}
}
class Node: Identifiable,ObservableObject {
@Published var id: UUID
@Published var position: CGPoint
@Published var positionProxy: CGPoint
init (id: UUID,position: CGPoint) {
self.id = id
self.position = position
self.positionProxy = position
}
}
struct NodeView: View {
@Observedobject var node: Node
init(node: Node) {
self.node = node
}
var draggingNode: some Gesture {
DragGesture(coordinateSpace: .global)
.onChanged { value in
self.node.position.x = value.translation.width + self.node.positionProxy.x;
self.node.position.y = -value.translation.height + self.node.positionProxy.y
}
.onEnded { value in
self.node.position.x = value.translation.width + self.node.positionProxy.x;
self.node.position.y = -value.translation.height + self.node.positionProxy.y;
self.node.positionProxy = self.node.position
}
}
var body: some View {
RoundedRectangle(cornerRadius: 20)
.fill(Color.white)
.frame(width: 100,height: 100)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(linewidth: 1)
.fill(Color.gray)
)
.position(node.position)
.gesture(draggingNode)
}
}
我的问题是所有创建的形状都出现在相同的预定义位置
position: CGPoint(x: 400,y: 400)
在屏幕上,我必须手动将它们移动到预定位置。我正在寻找一种在右键单击期间或上下文菜单位置中跟踪光标位置的方法,以将其用作节点位置并能够编写类似
的内容self.canvas.addNode(position: cursorPosition)
代替
self.canvas.addNode(position: CGPoint(x: 400,y: 400))
Swift中是否有任何功能,最好是在SwiftUI中可以解决我的问题?
解决方法
目前无法在 SwiftUI 中检测鼠标点击位置。您的问题的解决方法可能是使用 SwiftUI Gesture
的组合。
以下是可用于检测(简单或双击)左键单击位置的代码:
import SwiftUI
struct ClickGesture: Gesture {
let count: Int
let coordinateSpace: CoordinateSpace
typealias Value = SimultaneousGesture<TapGesture,DragGesture>.Value
init(count: Int = 1,coordinateSpace: CoordinateSpace = .local) {
precondition(count > 0,"Count must be greater than or equal to 1.")
self.count = count
self.coordinateSpace = coordinateSpace
}
var body: SimultaneousGesture<TapGesture,DragGesture> {
TapGesture(count: count)
.simultaneously(with: DragGesture(minimumDistance: 0,coordinateSpace: coordinateSpace))
}
func onEnded(perform action: @escaping (CGPoint) -> Void) -> some Gesture {
ClickGesture(count: count,coordinateSpace: coordinateSpace)
.onEnded { (value: Value) -> Void in
guard value.first != nil else { return }
guard let startLocation = value.second?.startLocation else { return }
guard let endLocation = value.second?.location else { return }
guard ((startLocation.x-1)...(startLocation.x+1)).contains(endLocation.x),((startLocation.y-1)...(startLocation.y+1)).contains(endLocation.y) else { return }
action(startLocation)
}
}
}
extension View {
func onClickGesture(
count: Int,coordinateSpace: CoordinateSpace = .local,perform action: @escaping (CGPoint) -> Void
) -> some View {
gesture(ClickGesture(count: count,coordinateSpace: coordinateSpace)
.onEnded(perform: action)
)
}
func onClickGesture(
count: Int,perform action: @escaping (CGPoint) -> Void
) -> some View {
onClickGesture(count: count,coordinateSpace: .local,perform: action)
}
func onClickGesture(
perform action: @escaping (CGPoint) -> Void
) -> some View {
onClickGesture(count: 1,perform: action)
}
}
您可以以与 onTapGesture()
或 TapGesture
非常相似的方式使用它:
struct MyView: View {
var body: some View {
Rectangle()
.frame(width: 600,height: 400)
.onClickGesture(count: 2) { location in
print("Double tap at location \(location)")
}
}
}
您可以另外指定一个 CoordinateSpace。