简体   繁体   中英

How do I create a UIView with a transparent circle inside (in swift)?

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?

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:

  1. Create a view sized to the specified frame, with a black background set to 60% opacity
  2. Create the path for drawing the circle using the provided starting point and radius
  3. Create the mask for the area to remove
  4. 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: 在此处输入图片说明

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
}

The above solution works great.

Say if you are looking for mask with rectangle area here is the snippet below

let fWidth = self.frame.size.width
let fHeight = self.frame.size.height
let squareWidth = fWidth/2
let topLeft = CGPoint(x: fWidth/2-squareWidth/2, y: fHeight/2-squareWidth/2)
let topRight = CGPoint(x: fWidth/2+squareWidth/2, y: fHeight/2-squareWidth/2)
let bottomLeft = CGPoint(x: fWidth/2-squareWidth/2, y: fHeight/2+squareWidth/2)
let bottomRight = CGPoint(x: fWidth/2+squareWidth/2, y: fHeight/2+squareWidth/2)
let cornerWidth = squareWidth/4


// Step 2
let path = CGMutablePath()
path.addRoundedRect(in: CGRect(x: topLeft.x, y: topLeft.y,
  width: topRight.x - topLeft.x, height: bottomLeft.y - topLeft.y),
 cornerWidth: 20, cornerHeight: 20)


path.addRect(CGRect(origin: .zero, size: self.frame.size))
// Step 3
let maskLayer = CAShapeLayer()
maskLayer.backgroundColor = UIColor.clear.cgColor
maskLayer.path = path
// For Swift 4.0
maskLayer.fillRule = kCAFillRuleEvenOdd
// For Swift 4.2
//maskLayer.fillRule = .evenOdd
// Step 4
self.layer.mask = maskLayer
self.clipsToBounds = true

rectangle mask looks like this

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM