2

I am trying to implement an undo/redo method using NSUndoManager. I have asked other questions on this, but am still stuck.

Where I am at the moment is as follows:

.h
NSUndoManager *undoManager;
@property(nonatomic,retain) NSUndoManager *undoManager;

.m
@synthesize undoManager;

[undoManager setLevelsOfUndo:99]; viewdidload:

NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];      
[dnc addObserver:self selector:@selector(undoButtonTapped) name:@"undo" object:nil];   
[dnc addObserver:self selector:@selector(redoButtonTapped) name:@"redo" object:nil];

- (void)resetTheImage:(UIImage*)image
{
    NSLog(@"%s", __FUNCTION__);

   // image = savedImage.image;
    if (image != drawImage.image)
    {
        [[undoManager prepareWithInvocationTarget:self] resetTheImage];
        image = drawImage.image ;        
    } else {
        NSLog(@"That didn't work");
    }
}

- (void)undoButtonTapped {
    NSLog(@"%s", __FUNCTION__);
    [undoManager undo];
}

I get "That didn't work"...

I would appreciate help. I will post the answer to my original question when I figure out what I'm doing wrong.

---EDIT---

I have changed resetTheImage as follows:

- (void)resetTheImage:(UIImage*)image
{
    NSLog(@"%s", __FUNCTION__);

    image = savedImage.image;
    if (image != drawImage.image)
    {
        drawImage.image = image;
        savedImage.image = image;
        NSLog(@"undo image");
        [[self.undoManager prepareWithInvocationTarget:drawImage.image] image];        

    } else {
        NSLog(@"same image");
        savedImage.image = image;
    }
}

However, confusion rains - it may help if someone (Brad?, Justin?) can provide a bullet point list of the steps I need to take to get this working. For example:

. Add notifications in ... . Trigger notifications.... . Have undo /redo buttons point to... . What methods/functions I really need to build.. ....

I wish SO would allow me to give you more points than 1..

(It doesn't help that my "o" key is getting wonky) It does help that I had a baby granddaughter yesterday :))))

ICL1901
  • 7,632
  • 14
  • 90
  • 138
  • 2
    Perhaps this is part of your problem: what's the purpose in your program of passing the parameter `image`, when the value passed by the caller is never read? (`image` is immediately set to `savedImage.image`). – justin Apr 18 '12 at 20:21
  • 1
    I agree with Justin, you probably don't want the parameter for an accessor called `-setImage:` to be named `image`. In any case, you never write that parameter anywhere, so its value is being lost. – Brad Larson Apr 18 '12 at 20:43
  • Thanks guys. I changed the method name to resetTheImage, but no joy. Should I post more code? I really do appreciate the help. – ICL1901 Apr 18 '12 at 21:40
  • You're still never writing `image` anywhere, so it's not being used for anything. Do you mean to say `drawImage.image = image` instead? Also, you're now missing a parameter for `-resetTheImage:` when preparing your invocation. – Brad Larson Apr 19 '12 at 16:16

1 Answers1

1

First, I want to thank everyone for any/all assistance. I solved this finally although I'm not sure if it's the best solution.

I made a UIView called from the UIViewController. The controls (colors and brushes) remain in the VC. The drawing methods move to the View Method.

The View Method calls a Drawing method to actually perform the draw, and the View method controls the undo/redo.

Here are some code snippets:

-(void)undoButtonClicked
{
    //NSLog(@"%s", __FUNCTION__);
    if ([self.currentArray count] == 0) {
        //nothing to undo
        return;
    }

    DrawingPath *undonePath = [self.currentArray lastObject];
    [self.currentArray removeLastObject];
    [self.redoStack addObject:undonePath];
    [self setNeedsDisplay];

}

-(void)redoButtonClicked
{
    //NSLog(@"%s", __FUNCTION__);

    if ([self.redoStack count] == 0) {
        // nothing to redo
        return;
    }

    DrawingPath *redonePath = [self.redoStack lastObject];
    [self.redoStack removeLastObject];
    [self.currentArray addObject:redonePath];
    [self setNeedsDisplay];

}

Let me know if anyone wants clarification. Thanks all again..

UPDATE as requested:

These are some headers:

    DrawingViewController  *mvc;
    NSMutableArray *pathArray;
    NSMutableArray *colorArray;
    NSMutableArray *bufferArray;
    NSMutableArray *currentArray;
    UIBezierPath *myPath;
    NSString *brushSize;
    CGPoint lastPoint;
    int colorIndex;
    NSString *colorKey;

    SoundEffect         *erasingSound;
    SoundEffect         *selectSound;

    BOOL swiped;    
    int moved;
    UIColor *currentColor;
    NSString *result;
}
@property(nonatomic,assign) NSInteger undoSteps;
@property (strong, nonatomic) NSString *result;

@property (strong,nonatomic) UIColor *currentColor;
@property (strong,nonatomic) NSMutableArray *currentArray;
@property (strong,nonatomic) NSMutableArray *bufferArray;
@property (strong,nonatomic) DrawingPath *currentColoredPath;
@property (strong,nonatomic) NSMutableArray *redoStack;
@property (strong, nonatomic) NSString *colorKey;

and here are some more of the methods.. The currentArray then keeps track of points, brush and color in a sort of stack. Undo removes from the stack, and adds into a temp stack that can be used to Redo.

-(void)undoButtonClicked
{
    //NSLog(@"%s", __FUNCTION__);
    if ([self.currentArray count] == 0) {
        //nothing to undo
        return;
    }

    DrawingPath *undonePath = [self.currentArray lastObject];
    [self.currentArray removeLastObject];
    [self.redoStack addObject:undonePath];
    [self setNeedsDisplay];

}

-(void)redoButtonClicked
{
    //NSLog(@"%s", __FUNCTION__);

    if ([self.redoStack count] == 0) {
        // nothing to redo
        return;
    }

    DrawingPath *redonePath = [self.redoStack lastObject];
    [self.redoStack removeLastObject];
    [self.currentArray addObject:redonePath];
    [self setNeedsDisplay];

}


#pragma mark - Touch Methods
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{   
    //NSLog(@"%s", __FUNCTION__);
    self.currentColoredPath = [[DrawingPath alloc] init];
    [self.currentColoredPath setColor:self.currentColor];
    UITouch *touch= [touches anyObject];


    [self.currentColoredPath.path moveToPoint:[touch locationInView:self]];
    [self.currentArray addObject:self.currentColoredPath];
    // Remove all paths from redo stack
    [self.redoStack removeAllObjects];

    lastPoint = [touch locationInView:self];
    lastPoint.y -= 20;

    if ([touch tapCount] == 2) {
        [self alertOKCancelAction];

        return;
    }  


}



-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    //NSLog(@"%s", __FUNCTION__);
    UITouch *touch = [touches anyObject]; 
    [self.currentColoredPath.path addLineToPoint:[touch locationInView:self]];

    [self setNeedsDisplay];


    CGPoint currentPoint = [touch locationInView:self];
    currentPoint.y -= 20;


    lastPoint = currentPoint;

}

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    //NSLog(@"%s", __FUNCTION__);
    self.currentColoredPath = nil;
}
ICL1901
  • 7,632
  • 14
  • 90
  • 138
  • Hi @David DelMonte, I need clarification on this, What your current array contains. How are you storing your paths? Please explain – Ranjit Jan 14 '14 at 05:59
  • Hi Ranjit. I will update my answer with the headers of the arrays I used. I'm sure by now there are better methods, but I've not had a bug report :) – ICL1901 Jan 14 '14 at 17:00
  • Hi David, thanks for your update, What I am presently doing is get image out of my context on each touch end and store it in a undo array. Here what is DrawingPath, is it a UIView or CGPath? – Ranjit Jan 14 '14 at 17:59
  • Hi David, I request you to look at this question, you will get an idea what I doing http://stackoverflow.com/questions/21027788/doing-undo-and-redo-with-cglayer-drawing – Ranjit Jan 14 '14 at 18:01
  • Yep, you are trying to work with the whole image. I managed to just save the various points. This post gives more info on how I approached this. http://soulwithmobiletechnology.blogspot.com/2011/06/redo-undo-in-paint-feature.html it's a little outdated, and you'll need to change the build settings, but you should be able to get the point. – ICL1901 Jan 14 '14 at 20:01
  • Just. An aside, but when someone tries to help you her, you give them points. If you don't people stop..;) – ICL1901 Jan 14 '14 at 22:40
  • Hello David, did I made any mistake in giving you points?. I am sorry if I have done that. can you tell me what more information you need? – Ranjit Jan 15 '14 at 05:31
  • My apologies Ranjit. I was tired last night. Don't worry about it. How is your work progressing? – ICL1901 Jan 15 '14 at 19:22
  • My drawing is not very smooth, so first I have decided to make the drawing smooth, I mean the writing is not very realistic like bamboo app with variable width. And as your approach is regarding storing points, so I dont know whether if I used a different algorithm then and if my points increase then again undo/redo operation will go for a toss. Correct me if I am wrong. If possible suggest me how I can do smooth drawing. – Ranjit Jan 16 '14 at 05:03
  • Hello @David, I am back to crack this issue, So you said that, your current array will keep track of color, width and points, so how does your drawRect method looks like – Ranjit Jan 29 '14 at 11:44
  • ,In my current implementation,In touches moved, I create a UIBezeirPath, I do all the stuff of move to point, add line to point, then I get a CGPath from it and in drawRect method I just draw that path into CGLayer, So I am not understanding how I can implement your logic. I need your suggestion. – Ranjit Jan 29 '14 at 12:01
  • Ranjit, you can contact me off list ddelmonteatmacdotcom, and I can send you the project. It's about 4mb. – ICL1901 Jan 30 '14 at 01:39
  • Hello @David, did you get my mail – Ranjit Jan 30 '14 at 09:22