Search code examples
iosswiftcashapelayer

CAShapeLayer groove path


I wanted to make a white color view with a triangle shaped pointer being grooved inside like this:

enter image description here

As shown in the image above, goal is to create a "rounded groove" inset into the whiteview

    let pointerRadius:CGFloat = 4
    pointerLayer = CAShapeLayer()
    pointerLayer.path = pointerPathForContentSize(contentSize: bounds.size).cgPath
    pointerLayer.lineJoin = kCALineJoinRound
    pointerLayer.lineWidth = 2*pointerRadius
    pointerLayer.fillColor = UIColor.white.cgColor
    pointerLayer.strokeColor = UIColor.black.cgColor
    pointerLayer.backgroundColor = UIColor.white.cgColor

    layer.addSublayer(pointerLayer)

But what I get is this:

enter image description here

But,if I set the stroke color to white

pointerLayer.strokeColor = UIColor.white.cgColor 

enter image description here In the groove I wanted to have a rounded edge in bottom (just like in the first pic) which no more remains visible when fillColor and strokeColor get matched (both white). How can I fix it? Is there any other way to achieve this?

Here is the code for pointer path:

private func pointerPathForContentSize(contentSize: CGSize) -> UIBezierPath
{
    let rect = CGRect(x: 0, y: 0, width: contentSize.width, height: contentSize.height)

    let width:CGFloat = 20
    let height:CGFloat = 20

    let path = UIBezierPath()


    let startX:CGFloat = 50
    let startY:CGFloat = rect.minY
    path.move(to: CGPoint(x: startX , y: startY))
    path.addLine(to: CGPoint(x: (startX + width*0.5), y: startY + height))


    path.addLine(to: CGPoint(x: (startX + width), y: startY))

    path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
    path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
    path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
    path.addLine(to: CGPoint(x: rect.minX, y: rect.minY))


    path.close()
    return path
}

Solution

  • Since you have already outlined the shape you want by stroking the path, I think the simplest solution is probably to use the stroked and filled path as a mask.

    For example, here is a rectangular red view:

    enter image description here

    And here is the same red view with the notch cut out of the top. This seems to be the sort of thing you're after:

    enter image description here

    What I did there was to mask the red view with a special mask view that draws the notch using .clear blend mode:

    class MaskView : UIView {
        override init(frame: CGRect) {
            super.init(frame:frame)
            self.isOpaque = false
        }
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        override func draw(_ rect: CGRect) {
            let con = UIGraphicsGetCurrentContext()!
            con.fill(self.bounds)
            con.setBlendMode(.clear)
            con.move(to: CGPoint(x:0, y:-4))
            con.addLine(to: CGPoint(x:100, y:-4))
            con.addLine(to: CGPoint(x:110, y:15))
            con.addLine(to: CGPoint(x:120, y:-4))
            con.addLine(to: CGPoint(x: self.bounds.maxX, y:-4))
            con.addLine(to: CGPoint(x: self.bounds.maxX, y:-20))
            con.addLine(to: CGPoint(x: 0, y:-20))
            con.closePath()
            con.setLineJoin(.round)
            con.setLineWidth(10)
            con.drawPath(using: .fillStroke) // stroke it and fill it
        }
    }
    

    So then when I'm ready to cut out the notch on the red view, I just say:

    self.redView.mask = MaskView(frame:self.redView.bounds)