我正在开发的应用程序允许用户管理一些资产。用户可以在屏幕上创建/删除/编辑/拆分/移动资产。用户需要能够撤消所有这些步骤。
资产是用核心数据管理的(是的,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
.
一切都很顺利,只有一个例外:除了我创建的组之外,Core Data 还创建了其他一些我无法控制的组。这就是我的意思:
我注册接收 NSUndoManagerDidCloseUndoGroupNotification 通知,如下所示:
- (void) registerUndoListener
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didCloseUndoGroup:)
name:NSUndoManagerDidCloseUndoGroupNotification object:nil];
...}
我使用这些通知来刷新撤消按钮并显示由于某些操作而被撤消的操作的名称:例如 Undo Split
然而,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 / enableUndoRegistration。这产生了一个异常:“无效状态,使用太多嵌套撤消组调用撤消”
以上都没有帮助我“隔离”我之前提到的神秘群体。
我不应该收到两次 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];
}