Search code examples
iosuiviewdrawingcalayercashapelayer

how to draw CALayer with line path like simulate eraser effect?


I want to simulate eraser effect with touch event to show a image that behind something block on top, eg, gray color;

Something like that:

enter image description here

I have find for a long time for the solution but I can't do it well.

following is my custom view code: CustomView.m:

-(id)initWithCoder:(NSCoder *)aDecoder
{
    if (self = [super initWithCoder:aDecoder]) {
        [self setup];
    }
    return self;
}

-(id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self setup];
    }
    return self;

}
-(void)setup
{
    [self setMultipleTouchEnabled:NO];
    [self setBackgroundColor:[UIColor darkGrayColor]];
    self.drawingPath = [UIBezierPath bezierPath];
    [self.drawingPath moveToPoint:CGPointZero];
    [self.drawingPath setLineWidth:5.0];

    self.image = [UIImage imageNamed:@"transformers.jpg"];
    self.shapeLayer = [CAShapeLayer layer];
    self.caLayer = [CALayer layer];
    self.caLayer.frame = self.bounds;
    self.caLayer.contents = (id)(self.image.CGImage);
    [self.layer addSublayer:self.caLayer];
}
- (void)drawRect:(CGRect)rect
{
    self.shapeLayer.path = [self.drawingPath CGPath];

    self.caLayer.mask = self.shapeLayer;
    self.shapeLayer.lineWidth = 5.0;

}

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    CGPoint location = [touch locationInView:self];

    [self.drawingPath moveToPoint:location];
    lastPt = location;
    [self setNeedsDisplay];

    NSLog(@"Touch Began");
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    CGPoint location = [touch locationInView:self];

    [self.drawingPath addLineToPoint:location];

    [self setNeedsDisplay];

}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self touchesMoved:touches withEvent:event];
}
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self touchesMoved:touches withEvent:event];
}

However it just simulate the behind image fill in the path. I want to be eraser effect like the picture. Please help.


Solution

  • Your code is close, but what you need is a custom layer class that has a gray background, and draws the path as transparent. You can do that with code like this

    RevealLayer.h

    @interface RevealLayer : CALayer
    @property (strong, nonatomic) UIBezierPath *drawingPath;
    @end
    

    RevealLayer.m

    @implementation RevealLayer
    - (UIBezierPath *)drawingPath
    {
        if ( !_drawingPath )
        {
            _drawingPath = [UIBezierPath new];
            [_drawingPath moveToPoint:CGPointZero];
            [_drawingPath setLineWidth:20.0];
            [_drawingPath setLineCapStyle:kCGLineCapRound];
        }
        return( _drawingPath );
    }
    
    - (void)drawInContext:(CGContextRef)context
    {
        UIGraphicsPushContext( context );
    
        [[UIColor darkGrayColor] set];
        CGContextFillRect( context, self.bounds );
    
        CGContextSetBlendMode( context, kCGBlendModeClear );
        [self.drawingPath stroke];
    
        UIGraphicsPopContext();
    }
    @end
    

    Then the code for the custom view class is similar to what you already have. However, the setup is a little different and you don't need to implement the drawRect: method.

    CustomView.m

    @interface CustomView()
    @property (strong, nonatomic) RevealLayer *revealLayer;
    @end
    
    @implementation CustomView
    - (void)setup
    {
        self.userInteractionEnabled = YES;
        [self setMultipleTouchEnabled:NO];
    
        self.image = [UIImage imageNamed:@"transformers.jpg"];
    
        self.revealLayer = [RevealLayer new];
        self.revealLayer.frame = self.bounds;
        self.revealLayer.backgroundColor = [[UIColor clearColor] CGColor];
        [self.revealLayer setNeedsDisplay];
    
        [self.layer addSublayer:self.revealLayer];
    }
    
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
        UITouch *touch = [touches anyObject];
        CGPoint location = [touch locationInView:self];
    
        [self.revealLayer.drawingPath moveToPoint:location];
        [self.revealLayer setNeedsDisplay];
    }
    
    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
    {
        UITouch *touch = [touches anyObject];
        CGPoint location = [touch locationInView:self];
    
        [self.revealLayer.drawingPath addLineToPoint:location];
        [self.revealLayer setNeedsDisplay];
    }