Example code programming Objective-C with Cocoa in Xcode and Interface Builder (Leopard)

51: Simple example of using the NSUndoManager

Problem: We have an application in which we drag a square accross a custom NSView. How do we make each step of that drag undo/redoable?

Program that employs simple undo to retrace each step in dragging a square accross a NSView.

Program that employs simple undo to retrace each step in dragging a square accross a NSView. The undo and redo buttons are set to repeat.

We need to use NSUndoManager. A discussion of the undo architecture is available here. A good tutorial (what used to exist at http://www.ex-cinder.com/downloads/undo-article.rtf) is to be found on CocoaDev. Another CocoaDev reference is here. Finally here are three interesting articles: How I learned to love NSUndoManager, Inspecting NSUndoManager's undo stack and Undology 101.

In this and the next three pages I will look at slightly different problem formulations: a simple undo within a view; (yet to be implemented) two simple undos for two views in different windows, (ditto) two simple undos for two views on the same window, (ditto) and finally a more complex case where I create my own NSUndoManager object.

Solution

In XCode create a new project XCode->File->New Project. Choose Cocoa Application and name it 051_Simple_Undo.
Code it as shown below and save.

Notice that the code in MyNSView.m contains both the
[undoManager registerUndoWithTarget:.....] and
[undoManager prepareWithInvocationTarget:....] forms of the 'use this method when doing the undo' commands.

// MyNSView.h #import <Cocoa/Cocoa.h> #import "MyPoint.h"; @interface MyNSView : NSView { MyPoint * myPointMouseLoc; NSPoint nsPointMouseLoc; CGFloat cgFloatLittleRectHalfSize; // size of rectangle mouse will drag NSRect nsRectSmallRect; // the rectangle the mouse will drag } - (void)setMyPointMouseLoc:(MyPoint *)pMouseLoc; @end // MyNSView.m #import "MyNSView.h" @implementation MyNSView - (id)initWithFrame:(NSRect)pFrame { if (! (self = [super initWithFrame:pFrame]) ) { NSLog(@"Error: MyNSView initWithFrame"); return self; } myPointMouseLoc = [[MyPoint alloc]initWithX:pFrame.size.width * 0.5 andY:pFrame.size.height * 0.5]; cgFloatLittleRectHalfSize = 10.0; nsRectSmallRect.size.width = cgFloatLittleRectHalfSize * 2.0; nsRectSmallRect.size.height = cgFloatLittleRectHalfSize * 2.0; return self; } -(void)mouseDown:(NSEvent *)pTheEvent { NSPoint zMousePointInWindow = [pTheEvent locationInWindow]; NSPoint tvarMousePointInView = [self convertPoint:zMousePointInWindow fromView:nil]; MyPoint * zPointMouseLoc = [[MyPoint alloc]initWithX:tvarMousePointInView.x andY:tvarMousePointInView.y]; [self setMyPointMouseLoc:zPointMouseLoc]; } // end mouseDown -(void)mouseDragged:(NSEvent *)pTheEvent { NSPoint zMousePointInWindow = [pTheEvent locationInWindow]; NSPoint tvarMousePointInView = [self convertPoint:zMousePointInWindow fromView:nil]; MyPoint * zPointMouseLoc = [[MyPoint alloc]initWithX:tvarMousePointInView.x andY:tvarMousePointInView.y]; [self setMyPointMouseLoc:zPointMouseLoc]; } // end mouseDragged -(void)mouseUp:(NSEvent *)pTheEvent { NSPoint zMousePointInWindow = [pTheEvent locationInWindow]; NSPoint tvarMousePointInView = [self convertPoint:zMousePointInWindow fromView:nil]; MyPoint * zPointMouseLoc = [[MyPoint alloc]initWithX:tvarMousePointInView.x andY:tvarMousePointInView.y]; [self setMyPointMouseLoc:zPointMouseLoc]; } // end mouseUp - (void)drawRect:(NSRect)pFrame { [[NSColor blackColor] set]; NSRectFill( pFrame ); nsRectSmallRect.origin.x = [myPointMouseLoc x] - cgFloatLittleRectHalfSize; nsRectSmallRect.origin.y = [myPointMouseLoc y] - cgFloatLittleRectHalfSize; NSBezierPath* zPath = [NSBezierPath bezierPathWithRect:nsRectSmallRect]; [[NSColor blueColor] set]; [zPath fill]; } // This is where we make the call // that tells the undo manager what to do when an undo is called - (void)setMyPointMouseLoc:(MyPoint * )pMouseLoc { // note that this also works //[[[self window] undoManager] registerUndoWithTarget:self // selector:@selector(setMyPointMouseLoc:) // object:myPointMouseLoc]; [[self undoManager] registerUndoWithTarget:self selector:@selector(setMyPointMouseLoc:) object:myPointMouseLoc]; // comment out the previous statement and uncomment the following // to use the alternative prepareWithInvocationTarget method // [[[self undoManager] prepareWithInvocationTarget:self] // setMyPointMouseLoc:myPointMouseLoc]; myPointMouseLoc = pMouseLoc; [self setNeedsDisplay:YES]; } @end // MyPoint.h #import <Cocoa/Cocoa.h> @interface MyPoint : NSObject { CGFloat x; CGFloat y; } @property CGFloat x; @property CGFloat y; - (id) initWithX:(CGFloat)pX andY:(CGFloat)pY; @end // MyPoint.m #import "MyPoint.h" @implementation MyPoint @synthesize x; @synthesize y; - (id) initWithX:(CGFloat)pX andY:(CGFloat)pY { if (! (self = [super init]) ) { NSLog(@"Error: MyPoint init"); return self; } x = pX; y = pY; return self; } @end

Click on the MainMenu.xib to go into Interface Builder.

Construct the window as shown. Set the custom view class to MyNSView. Click on each undo and redo button in turn and drag from the sent actions select to the First Responder as shown.

Select the undo button then drag from sent actions onto First responder

Interface Builder: select the undo button then drag from sent actions onto First responder

When the pop-up menu shows choose undo for the undo button and redo for the redo button.

Choose the undo option from the pop-up menu

Choose the undo option from the pop-up menu

The result is as shown.

The First Responder links the undo button to the undo action of the undo manager

The First Responder links the undo button to the undo action of the undo manager

Finally, in the Button Attributes part of the Inspector set each button's control mode to continuous.

Set the button mode to continuous

Set the button mode to continuous

Save and Run.

Drag the small square about several times and then hold the mouse down on the undo buton, then on the redo button and watch as the square moves back and forth along its original path.

Do the same using the Cmd-Z and Cmd-Shift_Z key combinations.

Program that employs simple undo to retrace each step in dragging a square accross a NSView.

Program that employs simple undo to retrace each step in dragging a square accross a NSView. The undo and redo buttons are set to repeat.

If you want to download the code

Click the Download Link to obtain 051_Simple_Undo.zip file of this whole OS X 10.5 Leopard program.

Download 051_Simple_Undo.zip (56K)



Please send me your comments

If you include your e-mail I may reply!  

Page last modified: 18:58 Sunday 12th. May 2013

Julius Guzy

Paintings & Drawings

  • Link to picture using new technique to represent looking through branches of a weeping willow by a river

animatedPaint