CALayer with transparent hole in it CALayer with transparent hole in it ios ios

CALayer with transparent hole in it


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.widthlet 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 = truelet fillLayer = CAShapeLayer()fillLayer.path = path.cgPathfillLayer.fillRule = kCAFillRuleEvenOddfillLayer.fillColor = Color.background.cgColorfillLayer.opacity = 0.5view.layer.addSublayer(fillLayer)

Swift 4.2 & 5:

let radius: CGFloat = myRect.size.widthlet 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 = truelet fillLayer = CAShapeLayer()fillLayer.path = path.cgPathfillLayer.fillRule = .evenOddfillLayer.fillColor = view.backgroundColor?.cgColorfillLayer.opacity = 0.5view.layer.addSublayer(fillLayer)


To create this effect, I found it easiest to create an entire view overlaying the screen, then subtracting portions of the screen using layers and UIBezierPaths. For a Swift implementation:

// Create a view filling the screen.let overlay = UIView(frame: CGRectMake(0, 0,     UIScreen.mainScreen().bounds.width,    UIScreen.mainScreen().bounds.height))// Set a semi-transparent, black background.overlay.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.85)// Create the initial layer from the view bounds.let maskLayer = CAShapeLayer()maskLayer.frame = overlay.boundsmaskLayer.fillColor = UIColor.blackColor().CGColor// Create the frame for the circle.let radius: CGFloat = 50.0let rect = CGRectMake(        CGRectGetMidX(overlay.frame) - radius,        CGRectGetMidY(overlay.frame) - radius,        2 * radius,        2 * radius)// Create the path.let path = UIBezierPath(rect: overlay.bounds)maskLayer.fillRule = kCAFillRuleEvenOdd// Append the circle to the path so that it is subtracted.path.appendPath(UIBezierPath(ovalInRect: rect))maskLayer.path = path.CGPath// Set the mask of the view.overlay.layer.mask = maskLayer// Add the view so it is visible.self.view.addSubview(overlay)

I tested the code above, and here is the result:

enter image description here

I added a library to CocoaPods that abstracts away a lot of the above code and allows you to easily create overlays with rectangular/circular holes, allowing the user to interact with views behind the overlay. I used it to create this tutorial for one of our apps:

Tutorial using the TAOverlayView

The library is called TAOverlayView, and is open source under Apache 2.0. I hope you find it useful!


Accepted solution Swift 3.0 compatible

let radius = myRect.size.widthlet path = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: self.mapView.bounds.size.width, height: self.mapView.bounds.size.height), cornerRadius: 0)let circlePath = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: 2.0*radius, height: 2.0*radius), cornerRadius: radius)path.append(circlePath)path.usesEvenOddFillRule = truelet fillLayer = CAShapeLayer()fillLayer.path = path.cgPathfillLayer.fillRule = kCAFillRuleEvenOddfillLayer.fillColor = UIColor.gray.cgColorfillLayer.opacity = 0.5view.layer.addSublayer(fillLayer)