3

I'm using [undoManager registerUndoWithTarget::] to add some changes to undo stack. However, sometimes happens, when in one run loop cycle two changes added to the same group, so they are reverted at once, which is not behavior I'd like to have. I want to separate these two changes to have two items in undo stack. How to correctly implement this? Use [NSObject performSelector: ] to call second undo addition in the next run loop cycle, or whatever else?

Thanks.

Nickolay Olshevsky
  • 13,706
  • 1
  • 34
  • 48

1 Answers1

5

As you’ve noticed, by default NSUndoManager automatically groups undo operations in one run loop cycle. You can change that behaviour, though: -[NSUndoManager setGroupsByEvent:] will disable automatic grouping if you send a NO argument. Note that you need to make sure that all groups are closed before the undo manager executes -undo or -undoNestedGroup. Since other Cocoa classes may want to register undo operations without explicitly creating a group, you can disable automatic grouping right before you register undo groups, reenabling after you’ve registered those groups.

For example:

- (void)someMethod {
    NSUndoManager *undoManager = …; // for example, [[self window] undoManager]
    [undoManager setGroupsByEvent:NO];
    {
        [undoManager beginUndoGrouping];
        {
            [undoManager registerUndoWithTarget:modelObject selector:@selector(setString1:) object:[modelObject string1]];
            [modelObject setStringValue:newValue1];
            [undoManager setActionName:@"String 1 Change"];
        }
        [undoManager endUndoGrouping];


        [undoManager beginUndoGrouping];
        {
            [undoManager registerUndoWithTarget:modelObject selector:@selector(setString2:) object:[modelObject string2]];
            [modelObject setString2:newValue3];

            [undoManager registerUndoWithTarget:modelObject selector:@selector(setString3:) object:[modelObject string3]];
            [modelObject setString3:newValue3];

            [undoManager setActionName:@"Strings 2 and 3 Change"];
        }
        [undoManager endUndoGrouping];
    }
    [undoManager setGroupsByEvent:YES];
}

In -someMethod, three changes are applied to modelObject, modifying its string1, string2 and string3 properties. The change applied to string1 is part of an undo group and the changes to string2 and string3 are part of another undo group. Both groups are enclosed in a block where the undo manager isn’t grouping all operations in the default undo group for the current run loop cycle. After this method is executed, the first undo operation undoes both string2 and string3 changes and subsequent undo operation undoes the change applied to string1, provided there wasn’t another undo group enclosing them.

I used C blocks {} to make these two hierarchies (no grouping by events and undo groups) clear.

NB: NSUndoManager is not thread-safe.

  • Thanks for the answer. The another part of problem (I should mention it in question) is that one part of undo registering messages is sent by NSTextView, which I cannot control. – Nickolay Olshevsky Jun 03 '13 at 10:03
  • @NickolayOlshevsky And what exact problem are you facing? I wrote a quick application with an `NSTextView` and it seems to work fine: text view undo operations are placed onto the undo the stack and are separate from my custom undo groups. –  Jun 03 '13 at 14:33
  • I'm editing some text in NSTextView, this records undo ('Undo Typing'). After that I'm dragging item to other location, registering undo for this dragging. And this dragging + text typing are recorded as the one Undo. Thanks for spending your time looking at this. – Nickolay Olshevsky Jun 03 '13 at 16:26
  • @NickolayOlshevsky Do you think you could post a small sample project that exhibits this behaviour? I’m willing to take a look at it. Are you registering the undo groups on the main thread? –  Jun 03 '13 at 16:28
  • Ohh, I will try to reproduce this in sample project later. Currently project to big to try to cut something out of it in reasonable time. Yes, everything works on main thread, I do not create any worker threads. And do not call any grouping methods - just [undoManager registerUndo:] – Nickolay Olshevsky Jun 03 '13 at 16:40
  • Wait; why aren’t you using the group methods as described in the answer? They’ll isolate your undo operation from other undo operations. –  Jun 03 '13 at 16:42
  • I tried it, with the same problem. 'Undo Typing' is mixed with my 'Item moving' undos. I think I need to dig deeper to my code. – Nickolay Olshevsky Jun 04 '13 at 09:02