【问题标题】:Undo/Redo for drawing in iOS在 iOS 中撤消/重做绘图
【发布时间】:2014-01-29 17:43:27
【问题描述】:

我正在开发一个绘图应用程序,我想做撤消/重做,为此我将触摸结束时的 CGPath 保存到 NSMutableArray,但我不明白如何在单击撤消按钮时呈现 CGPaths

编辑1:

由于我使用的是 BezierPaths,所以,我首先决定采用一种简单的方法,即在不使用 CGPath 的情况下抚摸这条路径,

EDIT2:由于我的撤消是在分段中进行的(即,部分而不是整个路径被删除),我决定创建一个数组数组,所以我做了相应的更改,现在我将在 CGlayer 中绘图,用CGPath

所以这里的“parentUndoArray”是数组数组。

所以我就这样做了

我有一个名为 DrawingPath 的类来进行绘图

//DrawingPath.h

@interface DrawingPath : NSObject

@property (strong, nonatomic) NSString    *pathWidth;
@property (strong,nonatomic) UIColor      *pathColor;
@property (strong,nonatomic) UIBezierPath *path;

- (void)draw;

@end

//DrawingPath.m

#import "DrawingPath.h"

@implementation DrawingPath


@synthesize pathWidth = _pathWidth;
@synthesize pathColor = _pathColor;
@synthesize path = _path;


- (id)init {

    if (!(self = [super init] ))
        return nil;



    _path = [[UIBezierPath alloc] init];
    _path.lineCapStyle=kCGLineCapRound;
    _path.lineJoinStyle=kCGLineJoinRound;

    [_path setLineWidth:2.0f];

    return self;
}

- (void)draw
{

    [self.pathColor setStroke];
    [self.path stroke];

}

所以现在在我的 DrawingView 中,我是这样做的

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
     ctr = 0;
    bufIdx = 0;
    UITouch *touch = [touches anyObject];
    pts[0] = [touch locationInView:self];
    isFirstTouchPoint = YES;

    [m_undoArray removeAllObjects];//On every touches began clear undoArray

}

-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
            UITouch *touch = [touches anyObject];

            CGPoint p = [touch locationInView:self];
            ctr++;
            pts[ctr] = p;


            if (ctr == 4)
            {
                pts[3] = midPoint(pts[2], pts[4]);


                for ( int i = 0; i < 4; i++)
                {
                    pointsBuffer[bufIdx + i] = pts[i];
                }

                bufIdx += 4;

                dispatch_async(drawingQueue, ^{


                    self.currentPath = [[DrawingPath alloc] init];
                    [self.currentPath setPathColor:self.lineColor];

                    if (bufIdx == 0) return;

                    LineSegment ls[4];
                    for ( int i = 0; i < bufIdx; i += 4)
                    {
                        if (isFirstTouchPoint) // ................. (3)
                        {

                            ls[0] = (LineSegment){pointsBuffer[0], pointsBuffer[0]};
                            [self.currentPath.path moveToPoint:ls[0].firstPoint];


                           // [offsetPath addLineToPoint:ls[0].firstPoint];
                            isFirstTouchPoint = NO;

                        }
                        else
                        {
                            ls[0] = lastSegmentOfPrev;

                        }


                        float frac1 = self.lineWidth/clamp(len_sq(pointsBuffer[i], pointsBuffer[i+1]), LOWER, UPPER); // ................. (4)
                        float frac2 = self.lineWidth/clamp(len_sq(pointsBuffer[i+1], pointsBuffer[i+2]), LOWER, UPPER);
                        float frac3 = self.lineWidth/clamp(len_sq(pointsBuffer[i+2], pointsBuffer[i+3]), LOWER, UPPER);


                        ls[1] = [self lineSegmentPerpendicularTo:(LineSegment){pointsBuffer[i], pointsBuffer[i+1]} ofRelativeLength:frac1]; // ................. (5)
                        ls[2] = [self lineSegmentPerpendicularTo:(LineSegment){pointsBuffer[i+1], pointsBuffer[i+2]} ofRelativeLength:frac2];
                        ls[3] = [self lineSegmentPerpendicularTo:(LineSegment){pointsBuffer[i+2], pointsBuffer[i+3]} ofRelativeLength:frac3];


                        [self.currentPath.path  moveToPoint:ls[0].firstPoint]; // ................. (6)
                        [self.currentPath.path  addCurveToPoint:ls[3].firstPoint controlPoint1:ls[1].firstPoint controlPoint2:ls[2].firstPoint];
                        [self.currentPath.path  addLineToPoint:ls[3].secondPoint];
                        [self.currentPath.path  addCurveToPoint:ls[0].secondPoint controlPoint1:ls[2].secondPoint controlPoint2:ls[1].secondPoint];
                        [self.currentPath.path  closePath];

                        lastSegmentOfPrev = ls[3]; // ................. (7)
                    }     


                    [m_undoArray addObject:self.currentPath];

                     EDIT:2 
                    CGPathRef cgPath = self.currentPath.path.CGPath;
                    mutablePath = CGPathCreateMutableCopy(cgPath);


                    dispatch_async(dispatch_get_main_queue(), ^{
                    bufIdx = 0;
                    [self setNeedsDisplay];

                        });
                    });


                pts[0] = pts[3];
                pts[1] = pts[4]; 
                ctr = 1;
            }
        }
    }
}

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{       
     [parentUndoArray addObject:m_undoArray];

}

下面是我的drawRect方法

编辑:现在我的 DrawRect 有两种情况

  - (void)drawRect:(CGRect)rect
{    
    switch (m_drawStep)
   {
       case DRAW:
       {

           CGContextRef context = UIGraphicsGetCurrentContext();//Get a reference to current context(The context to draw)



           CGContextRef layerContext = CGLayerGetContext(self.currentDrawingLayer);
           CGContextBeginPath(layerContext);
           CGContextAddPath(layerContext, mutablePath);
           CGContextSetStrokeColorWithColor(layerContext, self.lineColor.CGColor);
           CGContextSetFillColorWithColor(layerContext, self.lineColor.CGColor);
           CGContextSetBlendMode(layerContext,kCGBlendModeNormal);
           CGContextDrawPath(layerContext, kCGPathFillStroke);
          // CGPathRelease(mutablePath);



          CGContextDrawLayerInRect(context,rectSize, self.newDrawingLayer);
          CGContextDrawLayerInRect(context, self.bounds, self.permanentDrawingLayer);
          CGContextDrawLayerInRect(context, self.bounds, self.currentDrawingLayer );

          }
           break;




       case UNDO:
       {           
           for(int i = 0; i<[m_parentUndoArray count];i++)
           {
               NSMutableArray *undoArray = [m_parentUndoArray objectAtIndex:i];

               for(int i =0; i<[undoArray count];i++)
               {
                   DrawingPath *drawPath = [undoArray objectAtIndex:i];
                   [drawPath draw];
               }
           }


       }
           break;


       [super drawRect:rect];
}

EDIT2:现在我面临的问题是,即使我绘制小路径或大路径,数组数组中的数据都是相同的。但事实上,小路径应该包含较少的drawingPath对象,大路径应该包含更多的undoArray中的drawingPath对象,最后添加到名为“ParentUndoArray”的数组数组中

这是屏幕截图,

1.第一屏不抬手指,一气呵成的画线截图

2、执行一次undo操作后,只删除该行的一部分

【问题讨论】:

    标签: ios uikit core-graphics cgpath cglayer


    【解决方案1】:

    我已经找到了解决方案,我们需要创建一个数组DrawingPaths

    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
    {
       // Do the above code, then
       [m_undoArray addObject:self.currentPath];
    }
    
    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
    {
       [m_parentUndoArray addObject:[NSArray arrayWithArray:m_undoArray]];
    }
    

    然后在DrawRect中描边路径。

    【讨论】:

    【解决方案2】:

    如果您的 drawRect 方法可以绘制数组中的任何 CGPath,您只需在删除最后添加的 CGPath 后调用 setNeedsDisplay 在 Undo 方法中再次触发绘制

    【讨论】:

    • 你好 Niraj,如果你仔细看我的代码,你会注意到,我在 touchesMoved 中做了可变宽度,这里我只是从 UIBezeirPath 中取出 CgPath 并绘制它。
    • 可能,我没有很好地理解这个问题,但似乎您需要一种方法来在任何给定时间点重绘 NSMutableArray 中的路径。如果是这种情况,您可能应该将绘图代码从 touchesMoved 移动到单独的方法
    【解决方案3】:

    我认为您制作的路径对象超出了您的预期。我建议去你在触摸中分配 bezierPath 的地方并替换

    self.currentPath = [[DrawingPath alloc] init];
    

    if(!self.currentPath){
                        self.currentPath = [[DrawingPath alloc] init];
    }
    

    【讨论】:

    • 我试过了,如果我画两条线,然后如果我按撤消,那么第二行不会走,我有两次点击撤消3-4次然后第二行消失, 同样, 对于第一行
    • 那么撤销代码在哪里?不用担心我们可以做到这一点:)
    • 我会发布它,给我两分钟
    • 你好@Jef,检查我更新的问题,如果有任何疑问,请告诉我
    • 你有什么要说的?
    【解决方案4】:

    你应该这样做,因为它很容易

    - (id)initWithFrame:(CGRect)frame
    {
      self = [super initWithFrame:frame];
      if (self) {
        // Initialization code
    
        self.backgroundColor = [UIColor clearColor];
        myPath = [[UIBezierPath alloc] init];
        myPath.lineCapStyle = kCGLineCapRound;
        myPath.miterLimit = 0;
        bSize=5;
        myPath.lineWidth = bSize;
        brushPattern = [UIColor whiteColor];
    
        // Arrays for saving undo-redo steps in arrays
        pathArray = [[NSMutableArray alloc] init];
        bufferArray = [[NSMutableArray alloc] init];
    
    
      }
     return self;
    }
    
    // Only override drawRect: if you perform custom drawing.
    
    // An empty implementation adversely affects performance during animation.
    
    - (void)drawRect:(CGRect)rect
    {
        [brushPattern setStroke];
        for (id path in pathArray){
            if ([path isKindOfClass:[UIBezierPath class]]) {
                UIBezierPath *_path=(UIBezierPath *)path;
                [_path strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
            }
        }
    }
    
    #pragma mark - Touch Methods
    -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
    
             UITouch *mytouch = [[touches allObjects] objectAtIndex:0];
            myPath = [[UIBezierPath alloc] init];
            myPath.lineWidth = bSize;
            [myPath moveToPoint:[mytouch locationInView:self]];
            [pathArray addObject:myPath];
    
    }
    
    -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
    { 
        [myPath addLineToPoint:[[touches anyObject] locationInView:self]];
        [self setNeedsDisplay];
    }
    
    
    #pragma mark - Undo Method
    -(void)undoButtonClicked
    {
        if([pathArray count]>0)
        {
            if ([[pathArray lastObject] isKindOfClass:[SPUserResizableView class]])
            {
                [[pathArray lastObject] removeFromSuperview];
            }  
        UIBezierPath *_path = [pathArray lastObject];
        [bufferArray addObject:_path];
        [pathArray removeLastObject];
        [self setNeedsDisplay];
       }
    
    }
    -(void)setBrushSize: (CGFloat)brushSize
    {
        bSize=brushSize;
    }
    
    -(void)redoButtonClicked
    {
        if([bufferArray count]>0){
        UIBezierPath *_path = [bufferArray lastObject];
        [pathArray addObject:_path];
        [bufferArray removeLastObject];
        [self setNeedsDisplay];
        }
    }
     -(void)undoAllButtonClicked
    {
      [pathArray removeAllObjects];
      [self setNeedsDisplay];
    }
    

    希望它会有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-30
      • 2023-03-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-08
      相关资源
      最近更新 更多