получение позиции правого клика в приложении macos

Я работаю над приложением macOS со SwiftUI, которое должно иметь возможность создавать, упорядочивать и удалять геометрические фигуры на экране. Создание и перетаскивание фигур уже работает довольно хорошо с помощью контекстного меню.

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, которые решают мою проблему?


person Tony    schedule 27.08.2020    source источник


Ответы (1)


В настоящее время невозможно определить местоположение щелчка мыши в 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, coordinateSpace: .local, 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.

person Louis Lac    schedule 08.03.2021