撤消/重做以在iOS中绘图

时间:2014-01-29 17:43:28

标签: ios uikit core-graphics cgpath cglayer

我正在绘制一个绘图应用程序,我想做Undo / Redo,为此我在结束NSMutableArray的触摸上保存CGPath,但我不明白如何在点击撤消按钮时渲染CGPath

EDIT1:

当我使用BezierPaths时,我首先决定采用一种简单的方法来调整此路径,而不使用CGPath,

EDIT2:由于我的Undo发生在片段中(我,部分而不是整个路径被删除),我决定创建一个数组数组,所以我已经做了相应的更改,现在我将在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.第一个屏幕在不拉动手指的情况下一次性拍摄画线

enter image description here

2,执行撤消操作一次后,只删除该行的一段

enter image description here

4 个答案:

答案 0 :(得分:2)

我找到了一个解决方案,我们需要创建一个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中描述路径。

答案 1 :(得分:1)

如果你的drawRect方法可以绘制数组中的任何CGPath,你需要做的只是在删除最后添加的CGPath之后通过调用setNeedsDisplay在你的Undo方法中再次触发绘图

答案 2 :(得分:1)

我认为你制作的路径对象比你想要的多。我建议转到你在触摸中分配bezierPath的地方移动并替换

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

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

答案 3 :(得分:0)

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

- (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];
}

希望它会有所帮助。