Difficulty implementing NSUndoManager redo function
Asked Answered
A

1

6

I'm trying to implement an NSUndoManager in my iOS app. I got the undo functionality to work, but not the redo part. I'm quite new to iOS development and this is the first time that I've used NSUndoManager so it's probably something trivial.

My app is a painting/note taking app, I have a undo/redo stack with the last ten UIImages (I don't know if this is the most efficient way) in an array. When the user makes changes to the current image, the old image is pushed onto the stack, and the first image in the array is removed if the array already has ten objects. I have a int instance variable that I use to keep track of objects in the array and make sure that the correct image is displayed. My code looks like this:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    if (oldImagesArrays.count >= 10) {
        [oldImagesArrays removeObjectAtIndex:0];
    }
    UIImage * currentImage = pageView.canvas.image;
    if (currentImage != nil) {
        [oldImagesArrays addObject:currentImage];
        undoRedoStackIndex = oldImagesArrays.count -1;
    }
    [...]
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

    UIImage * currentImage = [oldImagesArrays lastObject];
    if (currentImage != pageView.canvas.image) {
        [undoManager registerUndoWithTarget:self selector:@selector(resetImage)  
        object:currentImage];
    }
}

// Gets called when the undo button is clicked
- (void)undoDrawing
{
    [undoManager undo];
    [undoManager registerUndoWithTarget:self 
                           selector:@selector(resetImage)
                             object:pageView.canvas.image];
    undoRedoStackIndex--;
}

// Gets called when the redo button is clicked
- (void)redoDrawing
{
    [undoManager redo];
    undoRedoStackIndex++;
}

- (void)resetImage
{
    NSLog(@"Hello"); // This NSLog message only appears when I click undo.
    pageView.canvas.image = [oldImagesArrays objectAtIndex:undoRedoStackIndex];
}

When I click the undo or redo buttons resetImage should get called, and set the current image to the next or previous object in my image stack (the current value of undoRedoStackIndex), this only happens when I click undo, but not redo.

Solutions && || better ways to do it would be appreciated.

Antilepton answered 23/11, 2011 at 21:46 Comment(1)
@JoshCaswell I have added some clarification to my question.Antilepton
A
7

You do not need to keep track of the changes, this is what the undo manager is for.

Make an undoable method like this:

- (void)setImage:(UIImage*)image
{
    if (_image != image)
    {
        [[_undoManager prepareWithInvocationTarget:self] setImage:_image]; // Here we let know the undo managed what image was used before
        [_image release];
        _image = [image retain];

        // post notifications to update UI
    }
}

This is it. To undo the change just call [_undoManager undo], to redo call [_undoManager redo]. When you tell the undo manager to undo it will call this method with the old image. If you use custom buttons for Undo operation you can validate it using [NSUndoManager canUndo], etc.

There is no limit for the number of undo operations. If you need to clean the undo stack at some point just call removeAllActions method.

Antihero answered 23/11, 2011 at 23:49 Comment(3)
Thank you, got it to work. Suspected that it was easier than I made it. Thanks. // AndersAntilepton
@Davyd hi, can you help me this this :) "post notifications to update UI" how to update the UIImage?Compete
A method with undo manager like I posted is supposed to be used within a data model. This is why I made a comment about posing a notification. For example: [[NSNotificationCentre defaultCentre] postNotification:XXMyDataModelDidChangeImageNotification object:self]. This means there should be a view controller somewhere with a view (UIImageView) that actually displays the image. This view controller should subscribe to the notification from the data model and update the view when the notifications is received. Alternatively a KVO may be used. I hope this helps.Antihero

© 2022 - 2024 — McMap. All rights reserved.