CALayer with a transparent hole in it

I have a simple view (left side of the picture), and I need to create some kind of overlay (to the right of the picture) to this view. This overlay should have some opacity, so the view below it is still partially visible. Most importantly, the overlay should have a round hole in the middle so that it does not overlap the center of the view (see the figure below).

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, for example:

 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 to combine these two layers so that they create the effect that I want. Anyone? I tried really everything ... Thanks so much for the help!

Image

+99
ios objective-c calayer quartz-core
May 12 '13 at
source share
5 answers

I was able to solve this with a proposal by John Steinmetz. If someone cares, here is 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 and 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) 
+192
May 13 '13 at 9:27
source share

To create this effect, it was easiest for me to create an entire view that overlays the screen and then subtract parts of the screen using layers and UIBezierPaths. For quick 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.bounds maskLayer.fillColor = UIColor.blackColor().CGColor // Create the frame for the circle. let radius: CGFloat = 50.0 let 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 checked the code above and here is the result:

enter image description here

I added the CocoaPods library, which abstracts many of the above code and makes it easy to create overlays with rectangular / round holes, allowing the user to interact with the views behind the overlay. I used it to create this tutorial for one of our applications:

Tutorial using the TAOverlayView

The library is called TAOverlayView and is open source in Apache 2.0. Hope you find this helpful!

+28
Mar 15 '16 at 15:13
source share

Swift 3.0 decision taken

 let radius = myRect.size.width let 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 = true let fillLayer = CAShapeLayer() fillLayer.path = path.cgPath fillLayer.fillRule = kCAFillRuleEvenOdd fillLayer.fillColor = UIColor.gray.cgColor fillLayer.opacity = 0.5 view.layer.addSublayer(fillLayer) 
+11
Oct 06 '16 at 13:59 on
source share

I used a similar approach like animal_chin, but I’m more visual, so I installed most of it in Interface Builder using outputs and automatic layout.

Here is my solution in Swift

  //shadowView is a UIView of what I want to be "solid" var outerPath = UIBezierPath(rect: shadowView.frame) //croppingView is a subview of shadowView that is laid out in interface builder using auto layout //croppingView is hidden. var circlePath = UIBezierPath(ovalInRect: croppingView.frame) outerPath.usesEvenOddFillRule = true outerPath.appendPath(circlePath) var maskLayer = CAShapeLayer() maskLayer.path = outerPath.CGPath maskLayer.fillRule = kCAFillRuleEvenOdd maskLayer.fillColor = UIColor.whiteColor().CGColor shadowView.layer.mask = maskLayer 
+10
Jul 07 '15 at 17:29
source share

Compatible Swift 2.0 Code

Starting with @animal_inch's answer, I am coding a bit of a useful class, hope this is appreciated:

 import Foundation import UIKit import CoreGraphics /// Apply a circle mask on a target view. You can customize radius, color and opacity of the mask. class CircleMaskView { private var fillLayer = CAShapeLayer() var target: UIView? var fillColor: UIColor = UIColor.grayColor() { didSet { self.fillLayer.fillColor = self.fillColor.CGColor } } var radius: CGFloat? { didSet { self.draw() } } var opacity: Float = 0.5 { didSet { self.fillLayer.opacity = self.opacity } } /** Constructor - parameter drawIn: target view - returns: object instance */ init(drawIn: UIView) { self.target = drawIn } /** Draw a circle mask on target view */ func draw() { guard (let target = target) else { print("target is nil") return } var rad: CGFloat = 0 let size = target.frame.size if let r = self.radius { rad = r } else { rad = min(size.height, size.width) } let path = UIBezierPath(roundedRect: CGRectMake(0, 0, size.width, size.height), cornerRadius: 0.0) let circlePath = UIBezierPath(roundedRect: CGRectMake(size.width / 2.0 - rad / 2.0, 0, rad, rad), cornerRadius: rad) path.appendPath(circlePath) path.usesEvenOddFillRule = true fillLayer.path = path.CGPath fillLayer.fillRule = kCAFillRuleEvenOdd fillLayer.fillColor = self.fillColor.CGColor fillLayer.opacity = self.opacity self.target.layer.addSublayer(fillLayer) } /** Remove circle mask */ func remove() { self.fillLayer.removeFromSuperlayer() } } 

Then, wherever you are in your code:

 let circle = CircleMaskView(drawIn: <target_view>) circle.opacity = 0.7 circle.draw() 
+7
Oct. 12 '15 at 8:33
source share



All Articles