I want to create a view that looks like this:
I figure what I need is a uiview with some sort of mask, I can make a mask in the shape of a circle using a UIBezierpath, however I cannot invert this makes so that it masks everything but the circle. I need this to be a mask of a view and not a fill layer because the view that I intend to mask has a UIBlurEffect on it. The end goal is to animate this UIView overtop of my existing views to provide instruction.
Please note that I am using swift. Is there away to do this? If so, how?
You can use this function to create what you need.
func createOverlay(frame : CGRect)
{
let overlayView = UIView(frame: frame)
overlayView.alpha = 0.6
overlayView.backgroundColor = UIColor.blackColor()
self.view.addSubview(overlayView)
let maskLayer = CAShapeLayer()
// Create a path with the rectangle in it.
var path = CGPathCreateMutable()
let radius : CGFloat = 50.0
let xOffset : CGFloat = 10
let yOffset : CGFloat = 10
CGPathAddArc(path, nil, overlayView.frame.width - radius/2 - xOffset, yOffset, radius, 0.0, 2 * 3.14, false)
CGPathAddRect(path, nil, CGRectMake(0, 0, overlayView.frame.width, overlayView.frame.height))
maskLayer.backgroundColor = UIColor.blackColor().CGColor
maskLayer.path = path;
maskLayer.fillRule = kCAFillRuleEvenOdd
// Release the path since it's not covered by ARC.
overlayView.layer.mask = maskLayer
overlayView.clipsToBounds = true
}
Adjust the radius
and xOffset
and yOffset
to change the radius and position of the circle.
For Swift 3, here is rakeshbs' answer formatted so it returns the UIView needed:
func createOverlay(frame : CGRect, xOffset: CGFloat, yOffset: CGFloat, radius: CGFloat) -> UIView
{
let overlayView = UIView(frame: frame)
overlayView.alpha = 0.6
overlayView.backgroundColor = UIColor.black
// Create a path with the rectangle in it.
let path = CGMutablePath()
path.addArc(center: CGPoint(x: xOffset, y: yOffset), radius: radius, startAngle: 0.0, endAngle: 2 * 3.14, clockwise: false)
path.addRect(CGRect(x: 0, y: 0, width: overlayView.frame.width, height: overlayView.frame.height))
let maskLayer = CAShapeLayer()
maskLayer.backgroundColor = UIColor.black.cgColor
maskLayer.path = path;
maskLayer.fillRule = kCAFillRuleEvenOdd
// Release the path since it's not covered by ARC.
overlayView.layer.mask = maskLayer
overlayView.clipsToBounds = true
return overlayView
}
Updated again for Swift 4 & removed a few items to make the code tighter.
Please note that maskLayer.fillRule
is set differently between Swift 4 and Swift 4.2.
func createOverlay(frame: CGRect,
xOffset: CGFloat,
yOffset: CGFloat,
radius: CGFloat) -> UIView {
// Step 1
let overlayView = UIView(frame: frame)
overlayView.backgroundColor = UIColor.black.withAlphaComponent(0.6)
// Step 2
let path = CGMutablePath()
path.addArc(center: CGPoint(x: xOffset, y: yOffset),
radius: radius,
startAngle: 0.0,
endAngle: 2.0 * .pi,
clockwise: false)
path.addRect(CGRect(origin: .zero, size: overlayView.frame.size))
// Step 3
let maskLayer = CAShapeLayer()
maskLayer.backgroundColor = UIColor.black.cgColor
maskLayer.path = path
// For Swift 4.0
maskLayer.fillRule = kCAFillRuleEvenOdd
// For Swift 4.2
maskLayer.fillRule = .evenOdd
// Step 4
overlayView.layer.mask = maskLayer
overlayView.clipsToBounds = true
return overlayView
}
A rough breakdown on what is happening:
- Create a view sized to the specified frame, with a black background set to 60% opacity
- Create the path for drawing the circle using the provided starting point and radius
- Create the mask for the area to remove
- Apply the mask & clip to bounds
The following code snippet will call this and place a circle in the middle of the screen with radius of 50:
let overlay = createOverlay(frame: view.frame,
xOffset: view.frame.midX,
yOffset: view.frame.midY,
radius: 50.0)
view.addSubview(overlay)
Which looks like this: