Calayer with Transparent Hole in It

CALayer with transparent hole in it

I was able to solve this with Jon Steinmetz suggestion. If any one cares, here's the final solution:

int radius = myRect.size.width;
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, self.mapView.bounds.size.width, self.mapView.bounds.size.height) cornerRadius:0];
UIBezierPath *circlePath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius) cornerRadius:radius];
[path appendPath:circlePath];
[path setUsesEvenOddFillRule:YES];

CAShapeLayer *fillLayer = [CAShapeLayer layer];
fillLayer.path = path.CGPath;
fillLayer.fillRule = kCAFillRuleEvenOdd;
fillLayer.fillColor = [UIColor grayColor].CGColor;
fillLayer.opacity = 0.5;
[view.layer addSublayer:fillLayer];

Swift 3.x:

let radius = myRect.size.width
let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: self.mapView.bounds.size.width, height: self.mapView.bounds.size.height), cornerRadius: 0)
let circlePath = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 2 * radius, height: 2 * radius), cornerRadius: radius)
path.append(circlePath)
path.usesEvenOddFillRule = true

let fillLayer = CAShapeLayer()
fillLayer.path = path.cgPath
fillLayer.fillRule = kCAFillRuleEvenOdd
fillLayer.fillColor = Color.background.cgColor
fillLayer.opacity = 0.5
view.layer.addSublayer(fillLayer)

Swift 4.2 & 5:

let radius: CGFloat = myRect.size.width
let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: self.view.bounds.size.height), cornerRadius: 0)
let circlePath = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 2 * radius, height: 2 * radius), cornerRadius: radius)
path.append(circlePath)
path.usesEvenOddFillRule = true

let fillLayer = CAShapeLayer()
fillLayer.path = path.cgPath
fillLayer.fillRule = .evenOdd
fillLayer.fillColor = view.backgroundColor?.cgColor
fillLayer.opacity = 0.5
view.layer.addSublayer(fillLayer)

Adjust the position of layer with transparent hole when view change from portrait to landscape

It looks like you'll need to update the frame of your path whenever the user rotates their device. You're looking for something like the answer here.

If you need to animate along with the transition, try something like this:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { context in
// update frames here
}, completion: nil)
}

Create CAGradient Layer with transparent hole in it?

Don't add the CAShapeLayer to the view's layer, rather set it as the mask of the CAGradientLayer. Also don't forget to set the bounds of the gradient layer.

I had to make some modifications to get it to run in a playground, but this works for me:

let gradientStart = UIColor.orange
let gradientEnd = UIColor.blue

final class BorderedButton: UIButton {

private let gradient = CAGradientLayer()
private let shapeLayer = CAShapeLayer()

override func layoutSubviews() {
super.layoutSubviews()

let radius = bounds.size.height / 2
let outside = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: bounds.width, height: bounds.height), cornerRadius: radius)
let inside = UIBezierPath(roundedRect: CGRect(x: 3.0, y: 3.0, width: bounds.width - 6, height: bounds.height - 6), cornerRadius: radius - 3)
outside.append(inside)
outside.usesEvenOddFillRule = true
shapeLayer.path = outside.cgPath
gradient.frame = CGRect(x: 0, y: 0, width: bounds.size.width, height: bounds.size.height)
}

init(color: UIColor?, frame: CGRect = .zero) {
super.init(frame: frame)

let isGradient = color == nil

shapeLayer.fillRule = kCAFillRuleEvenOdd

//gradient part
gradient.colors = isGradient ? [gradientStart.cgColor, gradientEnd.cgColor] : [color!.cgColor, color!.cgColor]
gradient.startPoint = CGPoint(x: 0.2, y: 0.5)
gradient.endPoint = CGPoint(x: 1, y: 1)

gradient.mask = shapeLayer

layer.addSublayer(gradient)
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

How to make a transparent hole in UIVisualEffectView?

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



Related Topics



Leave a reply



Submit