0

I have a simple UIViewController with UIVisualEffectView presented over another controller using overCurrentContext.

enter image description here

and it is fine.

now I try to make a hole inside that view the following way:

class CoverView: UIView {
    private let blurView: UIVisualEffectView = {
        UIVisualEffectView(effect: UIBlurEffect(style: .dark))
    }()

    // MARK: - Internal

    func setup() {
        addSubview(blurView)
        blurView.snp.makeConstraints { maker in
            maker.edges.equalToSuperview()
        }
        blurView.makeClearHole(rect: CGRect(x: 100, y: 100, width: 100, height: 230))
    }
}

extension UIView {
    func makeClearHole(rect: CGRect) {
        let maskLayer = CAShapeLayer()
        maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
        maskLayer.fillColor = UIColor.black.cgColor

        let pathToOverlay = UIBezierPath(rect: bounds)
        pathToOverlay.append(UIBezierPath(rect: rect))
        pathToOverlay.usesEvenOddFillRule = true
        maskLayer.path = pathToOverlay.cgPath

        layer.mask = maskLayer
    }
}

But the effect is reversed than I expected, why?

enter image description here

I need everything around blurred the way how rectangle currently is. And the rect inside should be transparent.

EDIT::

I have studied everything from comments below, and tried another answer, but result is still the same. Why?;) I have no idea what is wrong.

private func makeClearHole(rect: CGRect) {
    let maskLayer = CAShapeLayer()
    maskLayer.fillColor = UIColor.black.cgColor

    let pathToOverlay = CGMutablePath()
    pathToOverlay.addRect(blurView.bounds)
    pathToOverlay.addRect(rect)
    maskLayer.path = pathToOverlay
    maskLayer.fillRule = .evenOdd
    blurView.layer.mask = maskLayer
}
Bartłomiej Semańczyk
  • 59,234
  • 49
  • 233
  • 358
  • 1
    Search for `how to invert a CALayer mask` and you'll find a solution to this. – Gustavo Conde Jun 02 '22 at 07:50
  • 1
    https://stackoverflow.com/a/23452614/341994 – matt Jun 02 '22 at 10:29
  • 1
    https://stackoverflow.com/a/42012371/341994 – matt Jun 02 '22 at 10:30
  • 1
    https://stackoverflow.com/a/14151384/341994 – matt Jun 02 '22 at 10:31
  • 1
    https://stackoverflow.com/a/8632731/341994 – matt Jun 02 '22 at 10:33
  • @matt please tell me one more thing. Does it changes anything if I want to cut hole in subview of view which is main view of my controller presented with `overCurrentContext`? – Bartłomiej Semańczyk Jun 02 '22 at 11:31
  • I don't know what you mean by "changes anything". As far as I know, a hole is a hole. But obviously, if you cut a hole in just a subview of the main view, you are _not_ cutting a hole in the main view itself, and so you will not see what's behind the main view. (You will see the main view through the subview.) But that has nothing to with the business of cutting holes; it's just a fact about how your views are arranged. – matt Jun 02 '22 at 12:14
  • Within UIViewController I assign `CoverView()` to `view` property and run `setup()` on it. I just present new VC with clear background, then I add blur view as a subview, and need to cut holes in that blur view to see what is behind. Is it possible that way? – Bartłomiej Semańczyk Jun 02 '22 at 12:18
  • @GustavoConde I will award a bounty of 200 for you if you answer my question. I do not know why it doesnt work. It should be simple‍♂️ – Bartłomiej Semańczyk Jun 02 '22 at 13:17

1 Answers1

1

Well I tested your code and the original code with makeClearHole function in the extension works fine! The problem lies somewhere else.

1- Change the CoverView as following*

class CoverView: UIView {
    private lazy var blurView: UIVisualEffectView = {
        let bv = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
        bv.frame = bounds
        return bv
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    // MARK: - Internal

    func setup() {
        addSubview(blurView)
        blurView.snp.makeConstraints { maker in
            maker.edges.equalToSuperview()
        }
        blurView.makeClearHole(rect: CGRect(x: 100, y: 100, width: 100, height: 230))
    }
}

2- Give a frame to coverView in your view controller The view controller you have is different. But you should give the CoverView instance a frame. This is how: (again, this is how I tested but your view controller is definitely different)

class ViewController: UIViewController {

    var label: UILabel!
    var coverView: CoverView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        label = UILabel()
        label.text = "HELLO WORLD"
        label.font = UIFont.systemFont(ofSize: 40, weight: .black)
        
        
        coverView = CoverView(frame: CGRect(x: 20, y: 200, width: 300, height: 400))
        
        view.addSubview(label)
        view.addSubview(coverView)
        
        label.snp.makeConstraints { make in
            make.center.equalTo(view)
        }
        
        coverView.snp.makeConstraints { make in
            make.width.equalTo(coverView.bounds.width)
            make.height.equalTo(coverView.bounds.height)
            make.leading.equalTo(view).offset(coverView.frame.minX)
            make.top.equalTo(view).offset(coverView.frame.minY)
        }
    }


}

** Result**

Transparent rect inside blur view

Asteroid
  • 1,049
  • 2
  • 8
  • 16