15

I'm trying to achieve the result shown in the image using swift 1.2 and xcode 6.

Basically I want to create a view with a shape cut in it to be able to see the the view below to make a tutorial for my app. I know how to create a circular shape but i don't know how to cut it out in a view. I need a complete example on how to do it please. Thanks in advance

Image

LamBronx
  • 153
  • 1
  • 1
  • 5
  • you need to create a mask on the overlay white view, then set its alpha around `0.5` and the job is pretty much done. – holex Apr 15 '15 at 13:23
  • @holex . Thanks for the answer but I've got no problems about alpha to show what's below the view. The actual problem is that I don't know how to create a mask (or overlay) on a view (like the cropped circle on the view) – LamBronx Apr 15 '15 at 14:02
  • hey body, i found this tutorial that must be helpful for you: [Create a CALayer mask by combining multiple paths](http://www.raywenderlich.com/forums/viewtopic.php?f=2&t=7155) – duan Apr 20 '15 at 13:28

5 Answers5

34

Even though there is an answer, i'd like to share my way:

// Let's say that you have an outlet to the image view called imageView
// Create the white view 
let whiteView = UIView(frame: imageView.bounds) 
let maskLayer = CAShapeLayer() //create the mask layer

// Set the radius to 1/3 of the screen width
let radius : CGFloat = imageView.bounds.width/3 

// Create a path with the rectangle in it.
let path = UIBezierPath(rect: imageView.bounds)
// Put a circle path in the middle
path.addArcWithCenter(imageView.center, radius: radius, startAngle: 0.0, endAngle: CGFloat(2*M_PI), clockwise: true)

// Give the mask layer the path you just draw
maskLayer.path = path.CGPath
// Fill rule set to exclude intersected paths
maskLayer.fillRule = kCAFillRuleEvenOdd

// By now the mask is a rectangle with a circle cut out of it. Set the mask to the view and clip.
whiteView.layer.mask = maskLayer
whiteView.clipsToBounds = true

whiteView.alpha = 0.8
whiteView.backgroundColor = UIColor.whiteColor()

//If you are in a VC add to the VC's view (over the image)
view.addSubview(whiteView)    
// Annnnnd you're done.
Dănuț Mihai Florian
  • 3,075
  • 3
  • 26
  • 44
8

Here is sample code for how you can make a circle Mask for a UIView:

let sampleView = UIView(frame: UIScreen.mainScreen().bounds)
let maskLayer = CALayer()
maskLayer.frame = sampleView.bounds
let circleLayer = CAShapeLayer()
//assume the circle's radius is 100
circleLayer.frame = CGRectMake(sampleView.center.x - 100, sampleView.center.y - 100, 200, 200)
let circlePath = UIBezierPath(ovalInRect: CGRectMake(0, 0, 200, 200))
circleLayer.path = circlePath.CGPath
circleLayer.fillColor = UIColor.blackColor().CGColor
maskLayer.addSublayer(circleLayer)

sampleView.layer.mask = maskLayer

Here is what I made in the playground:

enter image description here

duan
  • 8,515
  • 3
  • 48
  • 70
  • That code won't work unless you set the fill rule on your circleLayer to kCAFillRuleEvenOdd. (Setting the fill rule on the bezier path doesn't work because apparently CGPath doesn't support fill rules like bezier paths do.) – Duncan C Apr 15 '15 at 15:51
  • 1
    @Carrl Thanks a lot for the answer but i still have trouble. I used your code but all that it does is make the sampleView a circular view. I put your code in the viewDidLoad of my ViewController but it doesn't work like the image you posted in your answer. I don't know if I'm (probably) missing something but I'm really struggling on this and I will really appreciate any help. Thank you so much again. – LamBronx Apr 16 '15 at 07:12
  • @DuncanC Yes, you are right~, i must set kCAFillRuleEvenOdd to do layer subtract. But here i use 2 imageView to do a composition. I think its the same, what do you think? – duan Apr 16 '15 at 14:48
  • The effect is about the same. Your technique will make the image partly transparent and reveal the layer under it. Mine puts an opaque white overlay on the image. Both approaches have value. – Duncan C Apr 20 '15 at 10:41
  • You could have answered the OP. @LamBronx was right. All the code does is to mask the image in a circle. – Dănuț Mihai Florian Oct 11 '15 at 08:10
  • @LamBronx sorry for the late reply,the code from me only crops the picture into a circle, you have to put one same picture below to reach the effect you have mention – duan Oct 11 '15 at 15:56
8
    //assume you create a UIImageView and content image before execute this code
  let sampleMask = UIView()
    sampleMask.frame = self.view.frame
    sampleMask.backgroundColor =  UIColor.black.withAlphaComponent(0.6)
    //assume you work in UIViewcontroller
    self.view.addSubview(sampleMask)
    let maskLayer = CALayer()
    maskLayer.frame = sampleMask.bounds
    let circleLayer = CAShapeLayer()
    //assume the circle's radius is 150
    circleLayer.frame = CGRect(x:0 , y:0,width: sampleMask.frame.size.width,height: sampleMask.frame.size.height)
    let finalPath = UIBezierPath(roundedRect: CGRect(x:0 , y:0,width: sampleMask.frame.size.width,height: sampleMask.frame.size.height), cornerRadius: 0)
    let circlePath = UIBezierPath(ovalIn: CGRect(x:sampleMask.center.x - 150, y:sampleMask.center.y - 150, width: 300, height: 300))
    finalPath.append(circlePath.reversing())
    circleLayer.path = finalPath.cgPath
    circleLayer.borderColor = UIColor.white.withAlphaComponent(1).cgColor
     circleLayer.borderWidth = 1
    maskLayer.addSublayer(circleLayer)

    sampleMask.layer.mask = maskLayer

enter image description here

RAUL QUISPE
  • 800
  • 9
  • 13
  • I implemented this but how to animate this shape from a very big circle that is bigger than the screen size to this ? – Saeed Rahmatolahi Mar 04 '20 at 03:37
  • 1
    use this tuto for CATransaction and animate it: https://medium.com/@joncardasis/better-ios-animations-with-catransaction-72a7425673a6 – RAUL QUISPE Mar 11 '20 at 17:04
4

The easiest way to do this would be to create a png image with partly transparent white around the outside and a clear circle in the middle. Then stack 2 image views on top of each other, with the masking image on top, and set its "opaque" flag to false.

You could also do this by creating a CAShapeLayer and set it up to use a translucent white color, then install a shape that is the square with the hole cut out of it shape. You'd install that shape layer on top of your image view's layer.

The most general-purpose way to do that would be to create a custom subclass of UIImageView and have the init method of your subclass create and install the shape layer. I just created a gist yesterday that illustrated creating a custom subclass of UIImageView. Here is the link: ImageViewWithGradient gist

That gist creates a gradient layer. It would be a simple matter to adapt it to create a shape layer instead, and if you modified the layoutSubviews method you could make it adapt the view and path if the image view gets resized.

EDIT:

Ok, I took the extra step of creating a playground that creates a cropping image view. You can find that at ImageViewWithMask on github

The resulting image for my playground looks like this:

Image with circular mask

Community
  • 1
  • 1
Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • My method creates a shape layer that overlays a translucent white appearance on top of the image view. The other poster's answer creates a mask layer that makes the image view partly transparent, which shows the contents under it. If the contents under the image view are pure white, the effect is the same. There's value in both approaches. – Duncan C Apr 20 '15 at 14:46
1
class MakeTransparentHoleOnOverlayView: UIView {

    @IBOutlet weak var transparentHoleView: UIView!

    // MARK: - Drawing

    override func draw(_ rect: CGRect) {
        super.draw(rect)

        if self.transparentHoleView != nil {
            // Ensures to use the current background color to set the filling color
            self.backgroundColor?.setFill()
            UIRectFill(rect)

            let layer = CAShapeLayer()
            let path = CGMutablePath()

            // Make hole in view's overlay
            // NOTE: Here, instead of using the transparentHoleView UIView we could use a specific CFRect location instead...
            path.addRect(transparentHoleView.frame)
            path.addRect(bounds)

            layer.path = path
            layer.fillRule = kCAFillRuleEvenOdd
            self.layer.mask = layer
        }
    }

    override func layoutSubviews () {
        super.layoutSubviews()
    }

    // MARK: - Initialization

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
    }
}
mazorati
  • 2,031
  • 3
  • 22
  • 22