You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS多点触控绘图的撤销与重做功能实现求助

Implementing Undo/Redo for Your Drawing App (Apple Notes Style)

Hey there! Let's get that Apple Notes-style undo/redo functionality working for your drawing app. Based on your existing PathArray setup, we'll adjust things to handle history properly—whether you want to undo individual line segments or full strokes (just like Notes does).

Option 1: Undo/Redo Individual Line Segments

If your pathArray stores every tiny line segment drawn during touchesMoved, here's how to add undo/redo:

Step 1: Add History Arrays

First, add two new mutable arrays to your Draw class to track undo and redo history:

// In your Draw class header or class extension
@property (nonatomic, strong) NSMutableArray *undoArray;
@property (nonatomic, strong) NSMutableArray *redoArray;

Initialize them when your view is set up:

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.pathArray = [NSMutableArray array];
        self.undoArray = [NSMutableArray array];
        self.redoArray = [NSMutableArray array];
    }
    return self;
}

Step 2: Update Drawing Logic

When you finish a segment (in touchesEnded), add it to your main array and clear the redo history (since new drawing invalidates previous redo steps):

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    if (!drawable) return;
    
    UITouch *touch = [touches anyObject];
    CGPoint currentPoint = [touch locationInView:self];
    
    // Create and add the new segment to your main array
    PathArray *newSegment = [[PathArray alloc] initWithStartPoint:lastPoint 
                                                           andEnd:currentPoint 
                                                         andColor:strokecolor 
                                                     andPathWidth:pathWidth];
    [self.pathArray addObject:newSegment];
    
    // Clear redo history—new drawing means we can't redo old steps anymore
    [self.redoArray removeAllObjects];
    
    [self setNeedsDisplay];
    [super touchesEnded:touches withEvent:event];
}

Step 3: Implement Undo/Redo Methods

Add these methods to handle the undo and redo actions:

// Undo the last drawn segment
- (void)undoDrawing {
    if (self.pathArray.count == 0) return; // Nothing to undo
    
    PathArray *lastSegment = [self.pathArray lastObject];
    [self.pathArray removeLastObject];
    [self.undoArray addObject:lastSegment];
    
    [self setNeedsDisplay];
}

// Redo the last undone segment
- (void)redoDrawing {
    if (self.undoArray.count == 0) return; // Nothing to redo
    
    PathArray *lastUndoneSegment = [self.undoArray lastObject];
    [self.undoArray removeLastObject];
    [self.pathArray addObject:lastUndoneSegment];
    
    [self setNeedsDisplay];
}

Option 2: Undo/Redo Full Strokes (Apple Notes Style)

Apple Notes undoes entire strokes (from when you press the stylus down to when you lift it), not individual segments. To match this behavior, we'll adjust your data structure to group segments into full strokes:

Step 1: Create a Stroke Class

First, make a new Stroke class to hold all segments of a single stroke:

// Stroke.h
#import <Foundation/Foundation.h>
#import "PathArray.h"

@interface Stroke : NSObject
@property (nonatomic, strong) NSMutableArray<PathArray *> *segments;
@property (nonatomic, strong) UIColor *color;
@property (nonatomic, assign) CGFloat pathWidth;
@end

// Stroke.m
#import "Stroke.h"

@implementation Stroke
- (instancetype)init {
    self = [super init];
    if (self) {
        self.segments = [NSMutableArray array];
    }
    return self;
}
@end

Step 2: Update Draw Class Arrays

Replace your pathArray with stroke-specific arrays, plus a reference to the current active stroke:

// In your Draw class
@property (nonatomic, strong) NSMutableArray<Stroke *> *strokeArray;
@property (nonatomic, strong) NSMutableArray<Stroke *> *undoStrokeArray;
@property (nonatomic, strong) NSMutableArray<Stroke *> *redoStrokeArray;
@property (nonatomic, strong) Stroke *currentStroke; // Track the stroke being drawn

Initialize them:

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.strokeArray = [NSMutableArray array];
        self.undoStrokeArray = [NSMutableArray array];
        self.redoStrokeArray = [NSMutableArray array];
    }
    return self;
}

Step 3: Update Touch Handling

Adjust your touch methods to build full strokes:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch* touch = [touches anyObject];
    if ([touch type] == UITouchTypeStylus) {
        if(drawable){
            [self.delegate changeScrollViewInteraction:NO];
            lastPoint = [touch locationInView:self];
            
            // Start a new stroke
            self.currentStroke = [[Stroke alloc] init];
            self.currentStroke.color = strokecolor;
            self.currentStroke.pathWidth = pathWidth;
        }
    } else {
        [self.delegate changeScrollViewInteraction:YES];
        return;
    }
    [super touchesBegan:touches withEvent:event];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    if (!drawable || !self.currentStroke) return;
    
    UITouch *touch = [touches anyObject];
    CGPoint currentPoint = [touch locationInView:self];
    
    // Add a segment to the current stroke
    PathArray *segment = [[PathArray alloc] initWithStartPoint:lastPoint 
                                                           andEnd:currentPoint 
                                                         andColor:self.currentStroke.color 
                                                     andPathWidth:self.currentStroke.pathWidth];
    [self.currentStroke.segments addObject:segment];
    
    lastPoint = currentPoint;
    [self setNeedsDisplay];
    [super touchesMoved:touches withEvent:event];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    if (!drawable || !self.currentStroke) return;
    
    // Add the completed stroke to the main array
    [self.strokeArray addObject:self.currentStroke];
    self.currentStroke = nil;
    
    // Clear redo history
    [self.redoStrokeArray removeAllObjects];
    
    [self setNeedsDisplay];
    [super touchesEnded:touches withEvent:event];
}

Step 4: Update drawRect

Modify your drawing method to render full strokes:

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetLineCap(context, kCGLineCapRound);
    
    for (Stroke *stroke in self.strokeArray) {
        // Set stroke properties once per full stroke
        CGContextSetStrokeColorWithColor(context, stroke.color.CGColor);
        CGContextSetLineWidth(context, stroke.pathWidth);
        
        // Draw all segments in the stroke
        for (PathArray *segment in stroke.segments) {
            CGContextBeginPath(context);
            CGContextMoveToPoint(context, segment.start.x, segment.start.y);
            CGContextAddLineToPoint(context, segment.end.x, segment.end.y);
            CGContextStrokePath(context);
        }
    }
}

Step 5: Implement Stroke-Based Undo/Redo

Add methods to undo/redo entire strokes:

- (void)undoDrawing {
    if (self.strokeArray.count == 0) return;
    
    Stroke *lastStroke = [self.strokeArray lastObject];
    [self.strokeArray removeLastObject];
    [self.undoStrokeArray addObject:lastStroke];
    
    [self setNeedsDisplay];
}

- (void)redoDrawing {
    if (self.undoStrokeArray.count == 0) return;
    
    Stroke *lastUndoneStroke = [self.undoStrokeArray lastObject];
    [self.undoStrokeArray removeLastObject];
    [self.strokeArray addObject:lastUndoneStroke];
    
    [self setNeedsDisplay];
}

Final Notes

  • You'll need to hook up these undoDrawing and redoDrawing methods to UI controls (like buttons or gesture recognizers) to let users trigger the actions.
  • Both options follow Apple Notes' behavior: if you undo a few steps and then draw something new, the redo history gets cleared (you can't redo steps that are now irrelevant to the current drawing).

内容的提问来源于stack exchange,提问作者Kiet Nguyen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 03:56:15