How to name Undo menu entries for Core Data add/remove items via bindings and NSArrayController?
Asked Answered
S

2

1

I have a NSTableView populated by a Core Data entity and Add Item / Remove Item buttons all wired with a NSArrayController and bindings in Interface Builder.

The Undo/Redo menu items can undo or redo the add / remove item actions.

But the menu entries are called only „Undo“ resp. „Redo“.
How can i name them like „Undo Add Item“, „Undo Remove Item“, etc.

(I am aware, something similar was asked before, but the accepted answers are either a single, now rotten link or the advice to subclass NSManagedObject and override a method that Apples documentation says about: "Important: You must not override this method.“)

Swinge answered 15/5, 2014 at 20:6 Comment(1)
There seems to be some solution in NSArrayController implement KVC method in NSArrayController insertObject: atArrangedObjectIndex: and removeObjectAtArrangedObjectIndex.Irritant
K
2

Add a subclass of NSArrayController as a file in your project. In the xib, in the Identity Inspector of the array controller, change the Class from NSArrayController to your new subclass.

Override the - newObject method.

- (id)newObject
{
    id newObj = [super newObject];

    NSUndoManager *undoManager = [[[NSApp delegate] window] undoManager];
    [undoManager setActionName:@"Add Item"];

    return newObj;
}

Also the - remove:sender method.

- (void)remove:(id)sender
{
    [super remove:sender];

    NSUndoManager *undoManager = [[[NSApp delegate] window] undoManager];
    [undoManager setActionName:@"Remove Item"];
}
Kinetic answered 16/5, 2014 at 3:58 Comment(1)
I think this is the simpler and more easy to maintain answer. Puts the logic where it belongs. Unless you start maintaining support for undo/redo names in the model (all entities have some sort of nice name for the undo menu), you either override the controller/view methods that get called by the user as you suggest, or you monitor the context for changes and have a huge ball of logic trying to describe every possible change. Watching the context's changes is a neat trick, though, and could be slick with enough support in the model.Combustible
C
0

Register for NSManagedObjectContextObjectsDidChangeNotification:

[[NSNotificationCenter defaultCenter] addObserver: self
                                         selector: @selector(mocDidChangeNotification:)
                              name:NSManagedObjectContextObjectsDidChangeNotification
                                           object: nil];

And parse the userInfo dictionary in the corresponding method:

- (void)mocDidChangeNotification:(NSNotification *)notification
{
    NSManagedObjectContext* savedContext = [notification object];

    // Ignore change notifications for anything but the mainQueue MOC
    if (savedContext != self.managedObjectContext) {
        return;
    }

    // Ignore updates -- lots of noise from maintaining user-irrelevant data

    // Set actionName for insertion
    for (NSManagedObject* insertedObject in 
           [notification.userInfo valueForKeyPath:NSInsertedObjectsKey])
    {
        NSString* objectClass = NSStringFromClass([insertedObject class]);
        savedContext.undoManager.actionName = savedContext.undoManager.isUndoing ? 
            [NSString stringWithFormat:@"Delete %@", objectClass] : 
            [NSString stringWithFormat:@"Insert %@", objectClass];
    }   

    // Set actionName for deletion
    for (NSManagedObject* deletedObject in 
           [notification.userInfo valueForKeyPath:NSDeletedObjectsKey])
    {
        NSString* objectClass = NSStringFromClass([deletedObject class]);
        savedContext.undoManager.actionName = savedContext.undoManager.isUndoing ? 
            [NSString stringWithFormat:@"Insert %@", objectClass] : 
            [NSString stringWithFormat:@"Delete %@", objectClass];
    }

}

I've tested this in my own code-- it's rough. Can spend a lot more time making the actionName nicer. I deleted parsing of updates because: 1) insertions and deletions of objects in to-many relationships generate updates of other objects 2) I don't care to figure out how to discover what properties changed at this time

I also have class names that aren't user-friendly, so this is a great time to implement the description function for all entities, and use that rather than the class name.

But this at least works for all object controllers in a project, and easily enough for insert and delete.

[edit] Updated with mikeD's suggestion to cover redo having an inverse name. Thanks!

Combustible answered 16/5, 2014 at 4:11 Comment(7)
That’s a nice idea. I use [[inserted/deletedObject entity] name] as string to add.Swinge
A Problem occures with redo: If i add an element — the undo menu item is called correctly „Undo Insert Entity“ — then undo it, the redo menu item is not named „Redo Insert Entity“ but „Redo Delete Entity“. Do you have any hint how to fix this?Swinge
Use the - isUndoing method in NSUndoManager to determine which ActionName to use.Kinetic
Updated the code sample above with isUndoing. I second the thanks to mikeD!Combustible
I'm beginning to wonder if trying to maintain an actionName method on my entities might be a neat trick to monitor changes to properties with KVO and have the entity class describe the change.Combustible
@Combustible That’s interesting. I will experiment with that idea.Swinge
@Combustible As i can only accept one answer, i go with mideD’s for the moment. But i really enjoyed your answer and learned a lot by experimenting with it. Also it did work great. +1Swinge

© 2022 - 2024 — McMap. All rights reserved.