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.“)

Community
  • 1
  • 1
MartinW
  • 4,966
  • 2
  • 24
  • 60
  • There seems to be some solution in NSArrayController implement KVC method in NSArrayController insertObject: atArrangedObjectIndex: and removeObjectAtArrangedObjectIndex. – Sandeep May 15 '14 at 21:48

2 Answers2

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"];
}
mikeD
  • 185
  • 1
  • 6
  • 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. – stevesliva May 18 '14 at 17:33
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!

stevesliva
  • 5,351
  • 1
  • 16
  • 39
  • That’s a nice idea. I use [[inserted/deletedObject entity] name] as string to add. – MartinW May 17 '14 at 21:19
  • 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? – MartinW May 17 '14 at 21:28
  • 2
    Use the `- isUndoing` method in NSUndoManager to determine which ActionName to use. – mikeD May 17 '14 at 23:41
  • Updated the code sample above with isUndoing. I second the thanks to mikeD! – stevesliva May 18 '14 at 17:24
  • 1
    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. – stevesliva May 18 '14 at 17:29
  • @stevesliva That’s interesting. I will experiment with that idea. – MartinW May 19 '14 at 10:06
  • @stevesliva 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. +1 – MartinW May 19 '14 at 10:08