One solution is to store the topmost tap in touchesBegan
and only draw this one.
As you have pointed out, you are not supposed to retain the UITouch
instance, so I recommend using a weak reference instead.
This will only draw a single touch. If you wish to draw the touches of multiple fingers, you need another way of filtering out the hand (many drawing apps have user settings for telling the app the pose of the hand, for example, but this is of course more complicated).
Here is an idea on how to do it:
#import <QuartzCore/QuartzCore.h>
@interface TViewController () {
// We store a weak reference to the current touch that is tracked
// for drawing.
__weak UITouch* drawingTouch;
// This is the previous point we drawed to, or the first point the user tapped.
CGPoint touchStartPoint;
}
@end
@interface _TDrawView : UIView {
@public
CGLayerRef persistentLayer, tempLayer;
}
-(void)commitDrawing;
-(void)discardDrawing;
@end
@implementation TViewController
- (void) loadView
{
self.view = [[_TDrawView alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.view.opaque = YES;
self.view.multipleTouchEnabled = YES;
self.view.backgroundColor = [UIColor whiteColor];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// Start with what we currently have
UITouch* topmostTouch = self->drawingTouch;
// Find the top-most touch
for (UITouch *touch in touches) {
CGPoint lastPoint = [touch locationInView:self.view];
if(!topmostTouch || [topmostTouch locationInView:self.view].y > lastPoint.y) {
topmostTouch = touch;
touchStartPoint = lastPoint;
}
}
// A new finger became the drawing finger, discard any previous
// strokes since last touchesEnded
if(self->drawingTouch != nil && self->drawingTouch != topmostTouch) {
[(_TDrawView*)self.view discardDrawing];
}
self->drawingTouch = topmostTouch;
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
// Always commit the current stroke to the persistent layer if the user
// releases a finger. This could need some tweaking for optimal user experience.
self->drawingTouch = nil;
[(_TDrawView*)self.view commitDrawing];
[self.view setNeedsDisplay];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
const CGFloat red=0, green=0, blue=0, brush=1;
for (UITouch *touch in touches) {
// Find the touch that we track for drawing
if(touch == self->drawingTouch) {
CGPoint currentPoint = [touch locationInView:self.view];
// Draw stroke first in temporary layer
CGContextRef ctx = CGLayerGetContext(((_TDrawView*)self.view)->tempLayer);
CGContextSetLineCap(ctx, kCGLineCapRound);
CGContextSetLineWidth(ctx, brush );
CGContextSetRGBStrokeColor(ctx, red, green, blue, 1.0);
CGContextSetBlendMode(ctx,kCGBlendModeNormal);
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, touchStartPoint.x, touchStartPoint.y);
CGContextAddLineToPoint(ctx, currentPoint.x, currentPoint.y);
CGContextStrokePath(ctx);
// Update the points so that the next line segment is drawn from where
// we left off
touchStartPoint = currentPoint;
// repaint the layer
[self.view setNeedsDisplay];
}
}
}
@end
@implementation _TDrawView
- (void) finalize {
if(persistentLayer) CGLayerRelease(persistentLayer);
if(tempLayer) CGLayerRelease(tempLayer);
}
- (void) drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
if(!persistentLayer) persistentLayer = CGLayerCreateWithContext(ctx, self.bounds.size, nil);
if(!tempLayer) tempLayer = CGLayerCreateWithContext(ctx, self.bounds.size, nil);
// Draw the persistant drawing
CGContextDrawLayerAtPoint(ctx, CGPointMake(0, 0), persistentLayer);
// Overlay with the temporary drawing
CGContextDrawLayerAtPoint(ctx, CGPointMake(0, 0), tempLayer);
}
- (void)commitDrawing {
// Persist the temporary drawing
CGContextRef ctx = CGLayerGetContext(persistentLayer);
CGContextDrawLayerAtPoint(ctx, CGPointMake(0, 0), tempLayer);
[self discardDrawing];
}
- (void)discardDrawing {
// Clears the temporary layer
CGContextRef ctx = CGLayerGetContext(tempLayer);
CGContextClearRect(ctx, self.bounds);
CGContextFlush(ctx);
}
@end
EDIT: I added the logic that if a new touch is detected, if there is currently any stroke being drawn with a higher y-value, it is removed, as we discussed in the comments.
The overlaying is done by painting two CGLayer
s. This code could be optimized a lot for performance, it should be looked at more as an illustration than production-ready code.