撤消和重做 NSPanGestureRecognizer 进行的 NSView 对象移动

Undoing and Redoing NSView Object Move Made by NSPanGestureRecognizer

我正在开发一个示例桌面应用程序来测试 UndoManager class,我不经常使用它。反正下面是思路。

  1. 我创建了两个 NSView 子视图对象(红色和蓝色)。它们被添加到 IBOutlet 连接的 NSView 对象 (panView)。
  2. 我为这两个子视图对象添加了平移手势(NSPanGestureRecognizer)。
  3. 当用户移动任一子视图对象时,应用程序调用撤消管理器。

以下是我从上到下的全部代码。

 import Cocoa

 class ViewController: NSViewController {
     // MARK: - Variables
     var myView1 = NSView()
     var myView2 = NSView()
     var uuid1 = String()
     var uuid2 = String()


     // MARK: - IBOutlet
     @IBOutlet weak var panView: NSView!


     // MARK: - Life cycle
     override func viewDidLoad() {
         super.viewDidLoad()

         /* myView */
         uuid1 = UUID().uuidString
         myView1 = NSView(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: 100, height: 100)))
         myView1.identifier = NSUserInterfaceItemIdentifier(rawValue: uuid1)
         myView1.wantsLayer = true
         if let myLayer1 = myView1.layer {
          myLayer1.backgroundColor = NSColor.red.cgColor
         }
         panView.addSubview(myView1)

         uuid2 = UUID().uuidString
         myView2 = NSView(frame: CGRect(origin: CGPoint(x: 400, y: 200), size: CGSize(width: 100, height: 100)))
         myView2.identifier = NSUserInterfaceItemIdentifier(rawValue: uuid2)
         myView2.wantsLayer = true
         if let myLayer2 = myView2.layer {
          myLayer2.backgroundColor = NSColor.blue.cgColor
         }
         panView.addSubview(myView2)

         /* pangesture */
         let panRecognizer1 = NSPanGestureRecognizer.init(target: self, action: #selector(panPictureView(_:)))
         let panRecognizer2 = NSPanGestureRecognizer.init(target: self, action: #selector(panPictureView(_:)))
         myView1.addGestureRecognizer(panRecognizer1)
         myView2.addGestureRecognizer(panRecognizer2)
     }         

     // MARK: - Pan gesture
     @objc func panPictureView(_ sender: NSPanGestureRecognizer) {
         let translation = sender.translation(in: self.view)
         if let movingObject = sender.view {
          let newPosition = CGPoint(x: movingObject.frame.origin.x + translation.x, y: movingObject.frame.origin.y + translation.y)
          movingObject.setFrameOrigin(newPosition)
          sender.setTranslation(CGPoint.zero, in: self.view)
          if sender.state == .began {
              if let rawID = sender.view?.identifier?.rawValue {
                  let dict = ["point": movingObject.frame.origin, "rawID": rawID] as [String : Any]
                  redoMoveObject(dict)
              }
          }
          else if sender.state == .ended {
              if let rawID = sender.view?.identifier?.rawValue {
                  let dict = ["point": movingObject.frame.origin, "rawID": rawID] as [String : Any]
                  undoMoveObject(dict)
              }
          }
         }
     }


     // MARK: - Undoing move
     @objc func undoMoveObject(_ newObject: [String : Any]) {
         undoManager?.registerUndo(withTarget: self, selector: #selector(redoMoveObject(_:)), object: newObject)
         undoManager?.setActionName("Move Object")
         if let point = newObject["point"] as? CGPoint, let rawID = newObject["rawID"] as? String {
          if rawID == uuid1 {
              myView1.frame.origin = point
          }
          else {
              myView2.frame.origin = point
          }
         }
     }

     @objc func redoMoveObject(_ newObject: [String : Any]) {
         undoManager?.registerUndo(withTarget: self, selector: #selector(undoMoveObject(_:)), object: newObject)
         undoManager?.setActionName("Move Object")
         if let point = newObject["point"] as? CGPoint, let rawID = newObject["rawID"] as? String {
          if rawID == uuid1 {
              myView1.frame.origin = point
          }
          else {
              myView2.frame.origin = point
          }
         }
     }
 }

有效。它不会崩溃。只是我必须按两次 Command + Z 才能撤消移动。我必须按 Command + Shift + Z 两次才能重做移动。所以我不知道这个停顿是从哪里来的。我做错了什么,你知道吗?谢谢。

撤消注册了两次:在 redoMoveObject 中的 sender.state == .beganundoMoveObject(dict) 中的 sender.state == .endedundoMoveObjectredoMoveObject做同样的事情,互相注册,它们可以合并成一个注册自己的函数。

示例:

// MARK: - Pan gesture
@objc func panPictureView(_ sender: NSPanGestureRecognizer) {
    if let movingObject = sender.view {
        if sender.state == .began { // register undo before first move
            if let rawID = movingObject.identifier?.rawValue {
                let dict = ["point": movingObject.frame.origin, "rawID": rawID] as [String : Any]
                undoMoveObject(dict)
            }
        }
        let translation = sender.translation(in: self.view)
        sender.setTranslation(CGPoint.zero, in: self.view)
        let newPosition = CGPoint(x: movingObject.frame.origin.x + translation.x, y: movingObject.frame.origin.y + translation.y)
        movingObject.setFrameOrigin(newPosition)
    }
}

// MARK: - Undoing move
@objc func undoMoveObject(_ newObject: [String : Any]) {
    if let point = newObject["point"] as? CGPoint, let rawID = newObject["rawID"] as? String {
        var movingObject: NSView
        if rawID == uuid1 {
            movingObject = myView1
        }
        else {
            movingObject = myView2
        }
        // register current frame origin for redo/undo
        let dict = ["point": movingObject.frame.origin, "rawID": rawID] as [String : Any]
        undoManager?.registerUndo(withTarget: self, selector: #selector(undoMoveObject(_:)), object: dict)
        undoManager?.setActionName("Move Object")
        // undo/redo
        movingObject.frame.origin = point
    }
}