我正在开发的应用程序允许用户管理一些资产。用户可以在屏幕上创建/删除/编辑/拆分/移动资产。用户需要能够撤消所有这些步骤。
资产是用核心数据管理的(是的,undoManager
是实例化的)。
对于这些操作中的每一个,我都用这对创建撤消分组:
beginUndoGrouping ... endUndoGrouping
Here's a simple example (sequence 1):
// SPLIT
- (void) menuSplitPiece: (id) sender
{
[self.managedObjectContext.undoManager beginUndoGrouping];
[self.managedObjectContext.undoManager setActionName:@"Split"];
//... do the split
[self.managedObjectContext.undoManager endUndoGrouping];
// if the user cancels the split action, call [self.managedObjectContext.undoManager undo] here;
}
我对编辑也是这样做的:如果用户取消了编辑,那么我会在endUndoGrouping
之后立即调用undo。
除了一个例外,一切都很顺利:除了我创建的组之外,还有其他由核心数据创建的组,我无法控制。我的意思是:
我注册接收NSUndoManagerDidCloseUndoGroupNotification通知,如下所示:
- (void) registerUndoListener
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didCloseUndoGroup:)
name:NSUndoManagerDidCloseUndoGroupNotification object:nil];
...}
我使用这些通知来刷新"撤消"按钮,并显示由于某些操作而撤消的操作的名称:例如,撤消拆分
然而,对于上面的每个操作,didCloseUndoGroup
被调用/通知两次(例如,第1节,在endUndoGrouping之后):
在第一次通知时,self.managedObjectContext.undoManager.undoActionName
包含我所期望的撤消操作名称,而第二次undoActionName
是一个空字符串。
作为一种变通方法,我试着简单地撤消有空名称的操作(假设它们不是我的,我不需要它们),看看我是否遗漏了什么。
现在,didCloseUndoGroup
看起来像这个
- (void) didCloseUndoGroup: (NSNotification *) notification
{
...
if ([self.managedObjectContext.undoManager.undoActionName isEqualToString:@""]){
[self.managedObjectContext.undoManager undo];
}
[self refreshUndoButton]; // this method displays the name of the undo action on the button
...
}
神奇的是,我可以使用"撤消"撤消任何命令,任何层数。但这不是它应该的工作方式。。。
在此之前,我尝试过其他几件事:
- [self.managedObjectContext processPendingChanges],然后再打开任何分组。它仍在发送两个通知
- 我尝试过的另一件事是disableUndoRegistration/ennableUndoRegulation。这一次生成了一个异常:"无效状态,使用过多嵌套的撤消组调用了撤消">
以上这些都没有帮助我"隔离"我之前提到的神秘分组。
我不应该两次收到NSUndoManagerDidCloseUndoGroupNotification通知。或者,我应该吗?有没有更好的方法来处理这种情况?
更新这就是最终成功的方法。以前,我一收到通知就会自动取消无名称组。这就是造成问题的原因。现在,我撤消所有操作,直到到达目标组,然后对该组执行最后一次撤消操作。
"undoManagerHelper"只是一个堆栈管理系统,它为推送到堆栈上的每个命令生成一个唯一的ID。我使用这个唯一的ID来命名组。
- (BOOL) undoLastAction
{
NSString *lastActionID = [self.undoManagerHelper pop]; // the command I'm looking for
if (lastActionID == nil) return false;
//... undo until there is nothing to undo or self.managedObjectContext.undoManager.undoActionName equals lastActionID
//the actual undo here
if ([currentActionID isEqualToString: lastActionID] && [self.managedObjectContext.undoManager canUndo]){
[self.managedObjectContext.undoManager undo];
}
return true;
}
- (void) beginUndoGroupingWithName: (NSString *) name
{
[self.managedObjectContext processPendingChanges];
[self.managedObjectContext.undoManager beginUndoGrouping];
NSString *actionID = [self.undoManagerHelper push: name];
[self.managedObjectContext.undoManager setActionName:actionID];
}
- (void) closeLastUndoGrouping
{
[self.managedObjectContext.undoManager endUndoGrouping];
[self.managedObjectContext processPendingChanges];
}
根据beginUndoGrouping的文档-https://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSUndoManager_Class/Reference/Reference.html-"默认情况下,撤消组在事件循环开始时自动开始,但您可以使用此方法开始自己的撤消组,并将它们嵌套在其他组中。"未命名组是包含所有操作的默认撤消组,听起来您应该根据情况忽略未命名组。