iosobjective-ccalayerquartz-core

CALayer with transparent hole in it


I have a simple view (left side of the picture) and i need to create some kind of overlay (right side of the picture) to this view. This overlay should have some opacity, so the view bellow it is still partly visible. Most importantly this overlay should have a circular hole in the middle of it so it doesn't overlay the center of the view (see picture bellow).

I can easily create a circle like this :

int radius = 20; //whatever
CAShapeLayer *circle = [CAShapeLayer layer];

circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0,radius,radius) cornerRadius:radius].CGPath;
circle.position = CGPointMake(CGRectGetMidX(view.frame)-radius,
                              CGRectGetMidY(view.frame)-radius);
circle.fillColor = [UIColor clearColor].CGColor;

And a "full" rectangular overlay like this :

CAShapeLayer *shadow = [CAShapeLayer layer];
shadow.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, view.bounds.size.width, view.bounds.size.height) cornerRadius:0].CGPath;
shadow.position = CGPointMake(0, 0);
shadow.fillColor = [UIColor grayColor].CGColor;
shadow.lineWidth = 0;
shadow.opacity = 0.5;
[view.layer addSublayer:shadow];

But I have no idea how can I combine these two layers so they create effect I want. Anyone? I've tried really everything... Thanks a lot for help!

Image


Solution

  • I was able to solve this with Jon Steinmetz suggestion. If any one cares, here's the final solution:

    int radius = myRect.size.width;
    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, self.mapView.bounds.size.width, self.mapView.bounds.size.height) cornerRadius:0];
    UIBezierPath *circlePath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius) cornerRadius:radius];
    [path appendPath:circlePath];
    [path setUsesEvenOddFillRule:YES];
    
    CAShapeLayer *fillLayer = [CAShapeLayer layer];
    fillLayer.path = path.CGPath;
    fillLayer.fillRule = kCAFillRuleEvenOdd;
    fillLayer.fillColor = [UIColor grayColor].CGColor;
    fillLayer.opacity = 0.5;
    [view.layer addSublayer:fillLayer];
    

    Swift 3.x:

    let radius = myRect.size.width
    let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: self.mapView.bounds.size.width, height: self.mapView.bounds.size.height), cornerRadius: 0)
    let circlePath = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 2 * radius, height: 2 * radius), cornerRadius: radius)
    path.append(circlePath)
    path.usesEvenOddFillRule = true
    
    let fillLayer = CAShapeLayer()
    fillLayer.path = path.cgPath
    fillLayer.fillRule = kCAFillRuleEvenOdd
    fillLayer.fillColor = Color.background.cgColor
    fillLayer.opacity = 0.5
    view.layer.addSublayer(fillLayer)
    

    Swift 4.2 & 5:

    let radius: CGFloat = myRect.size.width
    let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: self.view.bounds.size.height), cornerRadius: 0)
    let circlePath = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 2 * radius, height: 2 * radius), cornerRadius: radius)
    path.append(circlePath)
    path.usesEvenOddFillRule = true
    
    let fillLayer = CAShapeLayer()
    fillLayer.path = path.cgPath
    fillLayer.fillRule = .evenOdd
    fillLayer.fillColor = view.backgroundColor?.cgColor
    fillLayer.opacity = 0.5
    view.layer.addSublayer(fillLayer)