Undo/Redo 从未启用的菜单项

Undo/Redo Menu Items Never Enabled

我有一个核心数据应用程序,其中 NSTableView 绑定到 NSArrayController。我使用数组控制器管理添加和删除对象。我正在尝试添加 undo/redo 支持,以便当有人使用菜单项从 table 视图中删除对象时,他们可以撤消删除。

我的删除方法是:

- (IBAction)removeHost:(id)sender
{
    NSInteger row = [bookmarkList selectedRow];

    // Get the object so we can get to the attributes of the host
    NSArray *a = [bookmarksController arrangedObjects];
    NSManagedObject *object = [a objectAtIndex:row];

    if (!object) return;
    NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
    NSUndoManager *undoManager = [managedObjectContext undoManager];

    if (managedObjectContext.undoManager == nil)
    {
        NSLog(@"No undo manager in app controller!");
    } else {
        NSLog(@"We've got an undo manager in app controller!");
    }

    [undoManager registerUndoWithTarget:self selector:@selector(addBookmarkObject:) object:object];
    [bookmarksController removeObject:object];
    [undoManager setActionName:@"Bookmark Delete"];
}

删除对象可以正常工作,但撤消则不行。 Command-Z 菜单项永远不会启用。我设置了一个临时菜单项和操作来测试 undoManager,

- (IBAction)stupidUndoRemoveHost:(id)sender
{
    NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
    NSUndoManager *undoer = [managedObjectContext undoManager];

    NSLog(@"canUndo? %hhd", [undoer canUndo]);
    NSLog(@"canRedo? %hhd", [undoer canRedo]);
    NSLog(@"isUndoRegistrationEnabled? %hhd", [undoer isUndoRegistrationEnabled]);
    NSLog(@"undoMenuItemTitle = %@", [undoer undoMenuItemTitle]);
    NSLog(@"redoMenuItemTitle = %@", [undoer redoMenuItemTitle]);

    [undoer undo];
}

使用这个 IBAction 我可以撤消(嗯,有点,它添加了两次对象,所以很明显这里还有更多错误),但我只能撤消一次。如果我删除另一个对象 canUndo returns 0,而 stupidUndoRemoveHost 什么都不做。

我知道我不理解这里的内容。我在这里阅读的帖子多得数不过来,还有几篇博文和 Apple documentation。我以前做过这个,但那是十年前的事了,所以我的技术有点生疏了。非常感谢任何正确方向的帮助或指示。

更新:这里是 addBookmarkObject 方法:

- (void)addBookmarkObject: (NSManagedObject *)object
{
    [bookmarksController addObject:object];
}

这是来自 AppDelegate 的 windowWillReturnUndoManager

- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window {
    // Returns the NSUndoManager for the application. In this case, the manager returned is that of the managed object context for the application.
    NSUndoManager *undoManager = [[NSUndoManager alloc] init];
    self.persistentContainer.viewContext.undoManager = undoManager;

    if (self.persistentContainer.viewContext.undoManager == nil)
    {
        NSLog(@"No undo manager!");
    } else {
        NSLog(@"We've got an undo manager!");
    }

    return self.persistentContainer.viewContext.undoManager;
}

windowWillReturnUndoManager: 每次 Appkit 想要注册一个撤消操作和想要 enable/disable 撤消菜单项时被调用。如果 windowWillReturnUndoManager: returns 一个新的撤消管理器,则撤消堆栈为空并且撤消菜单项被禁用。

Core Data 会在删除对象时注册一个撤消操作,removeHost: 不应该注册一个额外的撤消操作。

- (IBAction)removeHost:(id)sender
{
    [bookmarksController remove:sender];
    [undoManager setActionName:@"Bookmark Delete"];
}

带有 Core Data 模板的 Xcode macOS Cocoa 应用程序存在一些缺陷。

NSWindowDelegate 方法 windowWillReturnUndoManager: 未被调用,因为在 xib 中,window 的委托未连接到应用程序委托。修复:将 window 的委托连接到委托。

self.persistentContainer.viewContext.undoManagernil。修复:创建持久容器时创建一次撤消管理器。

- (NSPersistentContainer *)persistentContainer {
    // The persistent container for the application. This implementation creates and returns a container, having loaded the store for the application to it.
    @synchronized (self) {
        if (_persistentContainer == nil) {
            _persistentContainer = [[NSPersistentContainer alloc] initWithName:@"TestCDUndo"];
            [_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
                if (error != nil) {
                    …
                    abort();
                }
                self->_persistentContainer.viewContext.undoManager = [[NSUndoManager alloc] init];
            }];
        }
    }

    return _persistentContainer;
}