0

I am trying to create an overlay view, that has a "cutout" part that comes from a frame that is passed to the view, that size and position of that passed frame will change upon creation of the view. And in that "cutout" part I am expecting to see the content that is under that overlay view. Tried to set border to a rounded rectangle that is added to a CGMutablePath, but no luck.

The expected result is something like this:

enter image description here

The code I currently have in my UIView class, without tried solutions as I can't seem to get them to work properly. This current code displays the expected result, but without the red border:

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

        UIColor.blue.setFill()
        UIRectFill(rect)

        let shapeLayer = CAShapeLayer()
        let path = CGMutablePath()

        // frame that will change position on the screen
        if let frame = changingFrame {
            path.addRoundedRect(in: frame, cornerWidth: 16, cornerHeight: 16)
        }

        path.addRect(bounds)
        shapeLayer.path = path
        shapeLayer.fillRule = CAShapeLayerFillRule.evenOdd
        self.layer.mask = shapeLayer
    }

I have tried solutions from here, here, but no luck as CAShapeLayer for border just overlays existing one.

What can I do differently to achieve the expected result? Thanks!

rdev
  • 148
  • 7

2 Answers2

0

try this ⭐️

If all you want to do is create a rounded rectangle, then you can simply use.

let rectangle = CGRect(x: 0, y: 0, width: 100, height: 100)
let path = UIBezierPath(roundedRect: rectangle, cornerRadius: 20)

enter image description here


enter image description here

view.clipsToBounds = true 
view.layer.cornerRadius = 10.0
let border = CAShapeLayer()
border.path = UIBezierPath(roundedRect:view.bounds, cornerRadius:10.0).cgPath
border.frame = view.bounds
border.fillColor = nil 
border.strokeColor = UIColor.purple.cgColor
border.lineWidth = borderWidth * 2.0 // doubled since half will be clipped
border.lineDashPattern = [1.0]
view.layer.addSublayer(border)
bdeviOS
  • 449
  • 1
  • 6
0

One approach is to use two sublayers... a "cutout" layer and a "border" layer.

Use the same path for the cutout and the border shape, setting the line width and stroke color for the "outline".

Here's an example -- including making it @IBDesignable with a few @IBInspectable properties:

@IBDesignable
class BorderedCutoutView: UIView {
    
    @IBInspectable
    var bkgColor: UIColor = .systemBlue {
        didSet {
            setNeedsLayout()
        }
    }

    @IBInspectable
    var brdColor: UIColor = .white {
        didSet {
            setNeedsLayout()
        }
    }
    
    @IBInspectable
    var brdWidth: CGFloat = 1 {
        didSet {
            setNeedsLayout()
        }
    }
    
    @IBInspectable
    var radius: CGFloat = 20 {
        didSet {
            setNeedsLayout()
        }
    }
    
    @IBInspectable
    var horizInset: CGFloat = 40.0 {
        didSet {
            setNeedsLayout()
        }
    }
    
    @IBInspectable
    var vertInset: CGFloat = 60.0 {
        didSet {
            setNeedsLayout()
        }
    }
    
    private let cutoutLayer = CAShapeLayer()
    private let borderLayer = CAShapeLayer()

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    
    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        backgroundColor = .clear
    }
    private func commonInit() -> Void {
        backgroundColor = .clear
        layer.addSublayer(cutoutLayer)
        layer.addSublayer(borderLayer)
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        let path = UIBezierPath(rect: bounds)
        let cp = UIBezierPath(roundedRect: bounds.insetBy(dx: horizInset, dy: vertInset), cornerRadius: radius)
        path.append(cp)
        path.usesEvenOddFillRule = true
        
        cutoutLayer.path = path.cgPath
        cutoutLayer.fillRule = .evenOdd
        cutoutLayer.fillColor = bkgColor.cgColor
        
        borderLayer.path = cp.cgPath
        borderLayer.fillColor = UIColor.clear.cgColor
        borderLayer.lineWidth = brdWidth
        borderLayer.strokeColor = brdColor.cgColor

    }

}

This example uses horizontal and vertical "inset" values to center the cutout in the view.

Result:

enter image description here

enter image description here

DonMag
  • 69,424
  • 5
  • 50
  • 86