Search code examples
ioscalayermaskcashapelayer

CAShapeLayer filling with another CAShapeLayer as a mask


My question is about filling animation one CAShapeLayer with another CAShapeLayer. So, I wrote some code that almost achieves my goal.

I created a based layer and fill it with another. Code below:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    _shapeLayer = [CAShapeLayer layer];
    [self drawBackgroundLayer];
}
// my main/backgound layer
- (void)drawBackgroundLayer {
    _shapeLayer.frame = CGRectMake(0, 0, 250, 250);
    _shapeLayer.lineWidth = 3;
    _shapeLayer.strokeColor = [UIColor blackColor].CGColor;
    _shapeLayer.fillColor = UIColor.whiteColor.CGColor;
    _shapeLayer.path = [UIBezierPath bezierPathWithRect:_shapeLayer.bounds].CGPath;

    [self.view.layer addSublayer:_shapeLayer];

    [self createCircleLayer];

}

Create a circled mask layer for filling:

- (void)createCircleLayer {

    _shapeLayer.fillColor = UIColor.greenColor.CGColor;

    CAShapeLayer *layer = [CAShapeLayer layer];

    CGFloat radius = _shapeLayer.bounds.size.width*sqrt(2)/2;

    layer.bounds = CGRectMake(0, 0, 2*radius, 2*radius);

    layer.position = CGPointMake(_shapeLayer.bounds.size.width/2, _shapeLayer.bounds.size.height/2);

    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:layer.bounds cornerRadius:radius];
    layer.path = path.CGPath;

    layer.transform = CATransform3DMakeScale(0.1, 0.1, 1);
    _shapeLayer.mask = layer;

}

// our result is next]

enter image description here

So, let's animate it. I just transform the layer to fill the whole "_shapeLayer".

- (void)animateMaskLayer:(CAShapeLayer *)maskLayer {

    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"];

    animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1, 1, 1.0)];
    animation.duration = 1;
    animation.removedOnCompletion = NO;
    animation.fillMode = kCAFillModeForwards;
    [newLayer addAnimation:animation forKey:@"transform"];
}

enter image description here

Now, I want to do the same with this green layer (fill it with red color). But when I use the mask I have red circle and the white background what is obviously. My goal here is to have red circle and green background. Like normal filling with another color. By using inverse mask won't help neither. So, I just have no ideas how to do it.

enter image description here

If you suggest using:

[_shapeLayer addSublayer:layer];

it won't work as there will be not only square but different paths and clipToBounds is not an option.

Thank you for any help!


Solution

  • So, the solution is simple. I just use two layers and one mask layer.

    First layer: Red layer I want to fill with green layer;

    - (void)drawBackgroundLayer {
        _shapeLayer.frame = CGRectMake(20, 20, 250, 250);
        _shapeLayer.lineWidth = 3;
        _shapeLayer.strokeColor = [UIColor blackColor].CGColor;
        _shapeLayer.lineWidth = 3;
        _shapeLayer.fillColor = UIColor.redColor.CGColor;
        _shapeLayer.path = [UIBezierPath bezierPathWithRect:_shapeLayer.bounds].CGPath;
    
        [self.view.layer addSublayer:_shapeLayer];
        [self drawUpperLayer];
    }
    

    Second Layer: Green Layer I use for filling.

    - (void)drawUpperLayer {
    
        _upperLayer = [CAShapeLayer layer];
        _upperLayer.frame = _shapeLayer.frame;
        _upperLayer.lineWidth = 3;
        _upperLayer.strokeColor = [UIColor blackColor].CGColor;
        _upperLayer.fillColor = UIColor.greenColor.CGColor;
        _upperLayer.path = [UIBezierPath bezierPathWithRect:_shapeLayer.bounds].CGPath;
    
        [_shapeLayer.superlayer addSublayer:_upperLayer];
    
    }
    

    Mask Layer:

    - (void)createMaskLayer {
    
        CAShapeLayer *layer = [CAShapeLayer layer];
        CGFloat radius = _upperLayer.bounds.size.width*sqrt(2)/2;
    
        layer.bounds = CGRectMake(0, 0, 2*radius, 2*radius);
        layer.fillColor = UIColor.blackColor.CGColor;
    
        layer.position = CGPointMake(_upperLayer.bounds.size.width/2, _upperLayer.bounds.size.height/2);
    
        UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:layer.bounds cornerRadius:radius];
        layer.path = path.CGPath;
    
        layer.transform = CATransform3DMakeScale(0.1, 0.1, 1);
    
        _upperLayer.mask = layer;
    }
    

    Animate mask layer:

    - (void)animateMaskLayer:(CAShapeLayer *)maskLayer {
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"];
        animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1, 1, 1.0)];
        animation.duration = 2;
        animation.removedOnCompletion = NO;
        animation.fillMode = kCAFillModeForwards;
        [maskLayer addAnimation:animation forKey:@"transform"];
    }
    

    So, resulted animation: enter image description here