swiftuiviewcalayermaskuivisualeffectview

Swift: UIVisualEffectView with rounded top edges and colored border


I am trying to setup a view with rounded corners but only the top left and right corner and with a colored border. This is my views class:

class PullUpOverview: UIVisualEffectView {

override init(effect: UIVisualEffect?) {
    super.init(effect: effect)

    let maskLayer = CAShapeLayer()
    maskLayer.path = UIBezierPath(roundedRect: bounds,
                                  byRoundingCorners: [.topRight, .topLeft],
                                  cornerRadii: CGSize(width: 25, height: 25)).cgPath
    layer.mask = maskLayer

    let borderLayer = CAShapeLayer()
    borderLayer.frame = bounds
    borderLayer.path = maskLayer.path
    borderLayer.lineWidth = 1
    borderLayer.strokeColor = UIColor.gray.cgColor
    borderLayer.fillColor = UIColor.clear.cgColor
    layer.addSublayer(borderLayer)
}

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

I add the view to my viewController view in the viewDidLoad method like this (overviewRect is correctly setup):

let overview = PullUpOverview(effect: UIBlurEffect(style: UIBlurEffect.Style.extraLight))
overview.frame = overviewRect
self.mapView.addSubview(overview)

But if I add the view nothing appears. If I change the PullUpOverview's super class to UIView and change my code in the viewDidLoad method to following the view and its mask is added and applied correctly (obviously the blur effect is missing that's why I tried to do the same with and UIVisualEffectView):

let overview = PullUpOverview(frame: overviewRect)
self.mapView.addSubview(overview)

So what do I have to change that I get an UIVisualEffectView with two rounded corners (both top corners) and a border layer?


Solution

  • The size is zero for bounds so both layers can not be visible. You can introduce a custom initializer as below,

    class PullUpOverview: UIVisualEffectView {
    
        init(effect: UIVisualEffect?, size: CGSize) {
            super.init(effect: effect)
    
            let rect = CGRect(origin: .zero, size: size)
    
            let maskLayer = CAShapeLayer()
            maskLayer.path = UIBezierPath(roundedRect: rect,
                                          byRoundingCorners: [.topRight, .topLeft],
                                          cornerRadii: CGSize(width: 25, height: 25)).cgPath
            maskLayer.frame = rect
            layer.mask = maskLayer
    
            let borderLayer = CAShapeLayer()
            borderLayer.frame = rect
            borderLayer.path = maskLayer.path
            borderLayer.lineWidth = 1
            borderLayer.strokeColor = UIColor.yellow.cgColor
            borderLayer.fillColor = UIColor.clear.cgColor
            layer.addSublayer(borderLayer)
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    }
    

    And now you can initialize and add this view as,

    let overviewRect = CGRect(origin: CGPoint(x: 100, y: 200), size: CGSize(width: 200, height: 200))
    let overview = PullUpOverview(effect: UIBlurEffect(style: .dark), size: overviewRect.size)
    overview.frame = overviewRect
    self.view.addSubview(overview)
    

    This will result into following,

    enter image description here