英文:
Undoing and Redoing NSView Object Move Made by NSPanGestureRecognizer
问题
我正在开发一个示例桌面应用程序,用于测试UndoManager
类,这是我不经常使用的。以下是我的想法。
- 我创建了两个
NSView
子视图对象(红色和蓝色)。它们被添加到一个IBOutlet连接的NSView
对象(panView)中。 - 我为这两个子视图对象添加了平移手势(
NSPanGestureRecognizer
)。 - 当用户移动任一子视图对象时,应用程序调用撤销管理器。
以下是我的整个代码,从上到下。
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]
undoMoveObject(dict)
}
}
else if sender.state == .ended {
if let rawID = sender.view?.identifier?.rawValue {
let dict = ["point": movingObject.frame.origin, "rawID": rawID] as [String : Any]
redoMoveObject(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才能重做移动操作。所以我不知道这个暂停是从哪里来的。你知道我做错了什么吗?谢谢。
英文:
I am working on a sample desktop application to test the UndoManager
class, which I don't often get to use. Anyway, the following is the idea.
- I create two
NSView
sub view objects (red & blue). They are added to an IBOutlet-connectedNSView
object (panView). - I add the pan gesture (
NSPanGestureRecognizer
) to these two sub view objects. - The applications calls the undo manager when the user moves either sub view object.
The following is my entire code from top to bottom.
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
}
}
}
}
It works. It doesn't crash. It's just that I have to press Command + Z twice to undo the move. And I have to press Command + Shift + Z twice to redo the move. So I don't know where this pause comes from. What am I doing wrong, do you know? Thanks.
答案1
得分: 0
撤销在redoMoveObject
中的sender.state == .began
和undoMoveObject(dict)
中的sender.state == .ended
都注册了两次。undoMoveObject
和redoMoveObject
执行相同的操作并相互注册,它们可以合并为一个函数来注册自身。
示例:
// MARK: - 拖动手势
@objc func panPictureView(_ sender: NSPanGestureRecognizer) {
if let movingObject = sender.view {
if sender.state == .began { // 在第一次移动之前注册撤销
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: - 撤销移动
@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
}
// 为重做/撤销注册当前框架原点
let dict = ["point": movingObject.frame.origin, "rawID": rawID] as [String : Any]
undoManager?.registerUndo(withTarget: self, selector: #selector(undoMoveObject(_:)), object: dict)
undoManager?.setActionName("移动对象")
// 撤销/重做
movingObject.frame.origin = point
}
}
英文:
Undo is registered twice: at sender.state == .began
in redoMoveObject
and at sender.state == .ended
in undoMoveObject(dict)
. undoMoveObject
and redoMoveObject
do the same thing and register each other, they can be merged into one function that registers itself.
Example:
// 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
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论