Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save aldo-jlaurenstin/2ed6569b1a3746645143 to your computer and use it in GitHub Desktop.
Save aldo-jlaurenstin/2ed6569b1a3746645143 to your computer and use it in GitHub Desktop.
Make a Transparent Hole on an Overlay UIView
//
// MakeTransparentHoleOnOverlayView.swift
//
// Created by James Laurenstin on 2015-04-10.
// Copyright (c) 2015 Aldo Group Inc. All rights reserved.
//
import UIKit
class MakeTransparentHoleOnOverlayView: UIView {
@IBOutlet weak var transparentHoleView: UIView!
// MARK: - Drawing
override func drawRect(rect: CGRect) {
super.drawRect(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 = CGPathCreateMutable()
// Make hole in view's overlay
// NOTE: Here, instead of using the transparentHoleView UIView we could use a specific CFRect location instead...
CGPathAddRect(path, nil, self.transparentHoleView.frame)
CGPathAddRect(path, nil, 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)
}
}
@aldo-jlaurenstin
Copy link
Author

This custom view allows to create a transparent hole using a child view's frame CGRect location.
I hope this helps someone. It's took me a while to figure this out properly.

@axmav
Copy link

axmav commented Oct 2, 2016

Thanks!
Swift 3 changes:

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)
    }
}

@richard182
Copy link

what can I do if I want a circle hole?

@ramunasjurgilas
Copy link

@richard182 Probably use path.addEllipse(in: <#T##CGRect#>)

@JDModi
Copy link

JDModi commented Jul 6, 2017

i am new in IOS . How to Use it Any idea ?

@shamshiranees
Copy link

shamshiranees commented Jan 3, 2018

I used this in touchesMoved delegate of UIView, This happens
simulator screen shot - iphone 8 plus - 2018-01-03 at 17 45 32

@barabashd
Copy link

Thanks!
This is first time GitHub helped me more then stack overflow

@neoneye
Copy link

neoneye commented Mar 23, 2018

Compiles fine with Xcode9.2 + Swift4. Tested on iOS9 simulator and iOS11 device.

Thank you for this super useful view.

@MorganBerger
Copy link

Hey! Thanks for this very useful view!
I have a question though, how can I add some corner radius to the hole view?
Thanks.

@CodeKunal
Copy link

I used this in touchesMoved delegate of UIView, This happens
simulator screen shot - iphone 8 plus - 2018-01-03 at 17 45 32

How did you add a corner radius to the cut view?

@vietstone-ng
Copy link

There's a simpler way:
Usage:

myOverlayView.makeClearHole(rect: CGRect(x: 100, y: 100, width: 200, height: 200))

Extension:

extension UIView {
    func makeClearHole(rect: CGRect) {
        let maskLayer = CAShapeLayer()
        maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
        maskLayer.fillColor = UIColor.black.cgColor
        
        let pathToOverlay = UIBezierPath(rect: self.bounds)
        pathToOverlay.append(UIBezierPath(rect: rect))
        pathToOverlay.usesEvenOddFillRule = true
        maskLayer.path = pathToOverlay.cgPath
        
        layer.mask = maskLayer
    }
}

@gavingt
Copy link

gavingt commented Feb 3, 2023

@CodeKunal Just call this method on your UIView, changing roundedRect and roundedRectPath arguments as needed:

extension UIView {
    func makeRoundedRectangularHole() {
        let entireViewPath = UIBezierPath(rect: self.bounds)
        let roundedRect = CGRect(x: 8, y: 8, width: bounds.width - 16, height: bounds.height - 16)
        let roundedRectPath = UIBezierPath(roundedRect: roundedRect, byRoundingCorners:.allCorners, cornerRadii: CGSize(width: 16.0, height: 16.0))
        entireViewPath.append(roundedRectPath)
        entireViewPath.usesEvenOddFillRule = true

        let maskLayer = CAShapeLayer()
        maskLayer.path = entireViewPath.cgPath
        maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
        maskLayer.fillColor = UIColor.black.cgColor
        layer.mask = maskLayer
    }
}

Thanks @vietstone-ng for the original code.

@murilogteixeira
Copy link

Thanks! This helped me solve a problem in my project.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment