转换绘图视图时,iOS绘图出错

时间:2014-11-19 22:00:39

标签: ios uiview drawrect

我有一个带UIPanGestureRecogniser的自定义UIView,用于捕获点和速度。我将这些点和速度存储在NSMutableArrays中,然后使用这些数据创建UIBezierPaths,然后将其添加到撤消/重做历史记录中。

当用户在屏幕上移动他们的手指时,撤消历史记录不断地添加新的路径,并且每个新路径被绘制到屏幕外图形上下文,剪切(到路径大小)然后在UIView上绘制。

我的问题是:现在我正在创建一个缩放缩放功能并将缩放和平移变换应用到绘图视图,放大时完成的任何绘图都会在错误的位置结束(向上)以及手指在屏幕上的左侧)和错误的尺寸(较小)。在撤消和重做之前,您无法真正看到绘图内容。我认为屏幕外环或屏幕的错误矩形正在更新或者撤消历史中存储的路径点由于变换而具有不同的原点/参考点(不知道该怎么称呼它!)。

这是我的第一个iOS应用程序(可能有一些愚蠢的错误),我已经使用了许多不同的教程来实现这一点,但我仍然坚持转换如何影响路径。以及如何确保路径以正确的比例在屏幕外的正确位置绘制。我试过改变路径,转换点并尝试反转变换,但我只是没有得到它。

所以这是代码(数量道歉)。我包括了捕捉点数,制作路径和绘图到屏幕的内容...一个捏合识别器将放大绘图视图,它将被翻译为放大到捏的中心。

在ViewController中,我创建了整个屏幕大小的绘图视图(VelocityDrawer)并添加了手势识别器:

VelocityDrawer *slv = [[VelocityDrawer alloc] initWithFrame:CGRectMake(0,0,768,1024)];
slv.tag = 100;
drawingView = slv;

drawingView.delegate = self;
drawingView.currentPen = finePen;

然后在VelocityDrawer中initWithFrame:(CGRect)frame

    self.undoHistory = [[NSMutableArray alloc] init];
    self.redoHistory = [[NSMutableArray alloc] init];

    // create offscreen context
    drawingContext = [self createOffscreenContext:frame.size];

    UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)];
    panGestureRecognizer.maximumNumberOfTouches = 1;
    [self addGestureRecognizer:panGestureRecognizer];

    UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
    tapGestureRecognizer.numberOfTapsRequired = 1;
    tapGestureRecognizer.numberOfTouchesRequired = 1;
    [self addGestureRecognizer:tapGestureRecognizer];

    [self clearHistoryBitmaps];

屏幕外的上下文是这样创建的:

- (CGContextRef) createOffscreenContext: (CGSize) size  {
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    float scaleFactor = [[UIScreen mainScreen] scale];

    // must use whole numbers so invalid context does not happen
    NSInteger sw = (NSInteger)(size.width * scaleFactor);
    NSInteger sh = (NSInteger)(size.height * scaleFactor);

    CGContextRef context = CGBitmapContextCreate(NULL,
                                                 sw,
                                                 sh,
                                                 8,
                                                 sw * 4,
                                                 colorSpace,
                                                 (CGBitmapInfo)kCGImageAlphaPremultipliedLast);
    CGColorSpaceRelease(colorSpace);

    CGContextScaleCTM(context, scaleFactor, scaleFactor);
    return context;
}

handlePanGesture。我捕获点,计算extractSize中线条的大小或宽度(基于手指速度),然后将信息添加到数组中:

    if (panGestureRecognizer.state == UIGestureRecognizerStateBegan)
    {
        CGPoint point = [panGestureRecognizer locationInView:panGestureRecognizer.view];
        CGPoint prev  = [[points firstObject] pos];
        float size;

        size = currentPen.minWidth/2;

        // Add point to array
        [self addPoint:point withSize:size];

        // Add empty group to history
        [undoHistory addObject:[[NSMutableArray alloc] init]];
    }

    if (panGestureRecognizer.state == UIGestureRecognizerStateChanged) {

        CGPoint point = [panGestureRecognizer locationInView:panGestureRecognizer.view];
        currentPoint = [(LinePoint *)[points lastObject] pos];

        float size = clampf([self extractSize:panGestureRecognizer], currentPen.minWidth, currentPen.maxWidth);
       [self addPoint:point withSize:size];

        NSMutableArray *pArr = [[NSMutableArray alloc] init];
        UIBezierPath *sizer = [[UIBezierPath alloc] init];

        // interpolate points to make smooth variable width line 
        NSMutableArray *interPoints = [self calculateSmoothLinePoints];

        // code continues

我循环遍历interPoints(来自识别器的最新点和前一点之间的插值点数组),创建将在屏幕上绘制的路径:

        // other code here
        // loop starts

        CGMutablePathRef path = CGPathCreateMutable();
        CGPathMoveToPoint(path, NULL, mid1.x, mid1.y);

        CGPathAddQuadCurveToPoint(path, NULL, prevT1.x, prevT1.y, mid2.x, mid2.y);
        CGPathAddLineToPoint(path, NULL, mid2b.x, mid2b.y);

        CGPathAddQuadCurveToPoint(path, NULL, prevB1.x, prevB1.y, mid1b.x, mid1b.y);
        CGPathAddLineToPoint(path, NULL, mid1.x, mid1.y);

        UIBezierPath *aPath = [UIBezierPath bezierPath];
        aPath.CGPath = path;

        [sizer appendPath:aPath];
        [pArr addObject:aPath];

        // more code here
        // loop ends

        CGPathRelease(path);

将所有路径添加到pArr后,我创建了一个HistoryItem,并用路径,线条颜色,线宽等填充它。

    HistoryItem *action = [[HistoryItem alloc] initWithPaths:pArr
                                               andLineColour:self.lineColor
                                                andLineWidth:self.lineWidth
                                                 andDrawMode:self.currentDrawMode
                                                    andScale:self.scale];

    [self addAction:action];

addAction向撤消堆栈添加HistoryItem。请注意我记录self.scale但不对其做任何事情。然后我得到边界矩形(drawBox)并调用setNeedsDisplayInRect

    CGRect drawBox = CGPathGetBoundingBox(sizer.CGPath);

    //Pad bounding box to respect line width
    drawBox.origin.x        -= self.lineWidth * 1;
    drawBox.origin.y        -= self.lineWidth * 1;
    drawBox.size.width      += self.lineWidth * 2;
    drawBox.size.height     += self.lineWidth * 2;

    [self setNeedsDisplayInRect:drawBox];

手势完成后,我在该线上添加一个圆头。代码省略。

最后drawRect:

- (void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();

    // offScreen context
    UIGraphicsPushContext(drawingContext);

    HistoryItem *action = [[undoHistory lastObject] lastObject];

    if(currentDrawMode == DRAW || currentDrawMode == ERASE) {
        for (UIBezierPath *p in action.pathsToDraw) {

            p.lineWidth = 1;
            p.lineCapStyle = kCGLineCapRound;

            [action.lineColor setFill];
            [action.lineColor setStroke];

            [p fill];
            [p stroke];
        }
    }
    if(currentDrawMode == UNDO) {
        CGContextClearRect(drawingContext, self.bounds);

        for (NSArray *actionGroup in undoHistory) {
            for (HistoryItem *undoAction in actionGroup) {
                for (UIBezierPath *p in undoAction.pathsToDraw) {
                    p.lineWidth = 1;
                    p.lineCapStyle = kCGLineCapRound;

                    [undoAction.lineColor setFill];
                    [undoAction.lineColor setStroke];

                    [p fill];
                    [p stroke];  
                }
            }
        }
    }
    // similar code for redo omitted

这里的框架/尺寸可能有问题吗?

    // Continuation of drawRect: 

    CGImageRef cgImage = CGBitmapContextCreateImage(drawingContext);
    CGContextClipToRect(context, rect);
    CGContextDrawImage(context, CGRectMake(0, 0, self.frame.size.width, self.frame.size.height), cgImage);
    CGImageRelease(cgImage);

    [super drawRect:rect];
}

1 个答案:

答案 0 :(得分:0)

重绘线条时,您需要对点进行相同的变换。存储在数组中的点相对于渲染的画布。我建议你存储原始帧,然后在下一行渲染中缩放原始帧和新大小之间的坐标。