views:

373

answers:

1

I'm creating a simple drawing application and would like some help with drawing rectangles based on the user's touch events. I'm able to draw a rectangle using Core Graphics from the point in touchesBegan to the point in touchesEnded. This rectangle is then displayed once the touchesEnded event is fired.

However, I would like to be able to do this in a 'live' fashion. Meaning, the rectangle is updated and displayed as the user drags their finger. Is this possible? Any help would be greatly appreciated, thank you!

UPDATE:

I was able to get this to work by using the following code. It works perfectly fine for small images, however it get's very slow for large images. I realize my solution is hugely inefficient and was wondering if anyone could suggest a better solution. Thanks.

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
        UITouch *touch = [touches anyObject];
        _firstPoint = [touch locationInView:self.view];
        _firstPoint.y -= 40;
        _lastPoint = _firstPoint;
    }

    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
    {
        UITouch *touch = [touches anyObject];
        CGPoint currentPoint = [touch locationInView:self.view];
        currentPoint.y -= 40;

        [self drawSquareFrom:_firstPoint to:currentPoint];

        _lastPoint = currentPoint;
    }

    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
    {
        UITouch *touch = [touches anyObject];
        CGPoint currentPoint = [touch locationInView:self.view];
        currentPoint.y -= 40;

        [self drawSquareFrom:_firstPoint to:currentPoint];

        [_modifiedImage release];
        _modifiedImage = [[UIImage alloc] initWithCGImage:_imageView.image.CGImage];
    }

    - (void)drawSquareFrom:(CGPoint)firstPoint to:(CGPoint)lastPoint
    {
        _imageView.image = _modifiedImage;

        CGFloat width  = lastPoint.x - firstPoint.x;
        CGFloat height = lastPoint.y - firstPoint.y;

        _path = [UIBezierPath bezierPathWithRect:CGRectMake(firstPoint.x, firstPoint.y, width, height)];

        UIGraphicsBeginImageContext( _originalImage.size );
        [_imageView.image drawInRect:CGRectMake(0, 0, _originalImage.size.width, _originalImage.size.height)];

        _path.lineWidth = 10;
        [[UIColor redColor] setStroke];
        [[UIColor clearColor] setFill];
        [_path fill];
        [_path stroke];

        _imageView.image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
    }
A: 

What you are doing right now is creating a paired CGImage and CGBitmapContext for every call to drawSquareFrom:to: and disposing them each time. Instead, create a single paired CGImage and CGBitmapContext that you reuse for each call.

-(void) drawSquareFrom:(CGPoint)from to:(CGPoint)to {
    CGContextRef context = [self offscreenContext];
    CGRect draw , full = [self offscreenContextBounds];

    draw.origin = from;
    draw.size.width = to.x - from.x;
    draw.size.height = to.y - from.y;
    draw = CGRectStandardize( draw );

    [[UIColor redColor] setStroke];
    [[UIColor clearColor] setFill];
    CGContextClearRect( context , full );
    CGContextFillRect( context , draw );
    CGContextStrokeRectWithWidth( context , draw , 10 );
    [_imageView setNeedsDisplay];
}

You create a paired CGImage and CGContext like this, although there are some ... to fill in. All the myX variables are intended to be class members.

-(CGContextRef) offscreenContext {
    if ( nil == myContext ) {
        size_t width = 400;
        size_t height = 300;
        CFMutableDataRef data = CFDataCreateMutable( NULL , width * height * 4 ); // 4 is bytes per pixel
        CGDataProviderRef provider = CGDataProviderCreateWithCFData( data );
        CGImageRef image = CGImageCreate( width , height , ... , provider , ... );
        CGBitmapContextRef context = CGBitmapContextCreate( CFDataGetMutableBytePtr( data ) , width , height , ... );
        CFRelease( data ); // retained by provider I think
        CGDataProviderRelease( provider ); // retained by image

        myImage = image;
        myContext = context;
        myContextFrame.origin = CGPointZero;
        myContextFrame.size.width = width;
        myContextFrame.size.height = height;
        _imageView.image = [UIImage imageWithCGImage:image];
    }

    return myContext;
}
-(CGRect) offscreenContextBounds {
    return myContextFrame;
}

This sets the _imageView.image to the newly created image. In drawSquareFrom:to: I assume that setNeedsDisplay is sufficient to draw the changes to made, but it may be that you need to assign a new UIImage each time that wraps the same CGImage.

drawnonward