Search code examples
iosswiftuibezierpathcashapelayer

How to Draw Custom Shape Swift?


I'm trying to create an arrow with rounded corners, but I'm not getting the result I want

private let arrowLayer: CAShapeLayer = {
    let layer = CAShapeLayer()
    layer.fillColor = UIColor.orange.cgColor
    layer.anchorPoint = CGPoint(x: 0.2, y: 0.5)
    layer.lineWidth = 4
    layer.strokeColor = UIColor.orange.cgColor
    layer.fillColor = UIColor.orange.cgColor
    layer.lineCap = .round
    layer.lineJoin = .round

    return layer
}()
override func layoutSubviews() {
    super.layoutSubviews()
    arrowLayer.bounds.size = CGSize(width: bounds.width * 0.27, height: bounds.width * 0.02)
    arrowLayer.position = CGPoint(x: bounds.midX, y: bounds.midY)

    let path = UIBezierPath()
    path.move(to: CGPoint(x: arrowLayer.bounds.width, y: arrowLayer.bounds.height * 0.3))
    path.addLine(to: CGPoint(x: arrowLayer.bounds.width, y: arrowLayer.bounds.height * 0.7))
    path.addLine(to: CGPoint(x: 0.0, y: arrowLayer.bounds.height))
    path.addLine(to: CGPoint(x: 0.0, y: 0.0))
    path.close()

    arrowLayer.path = path.cgPath
}

This is the result I get

enter image description here

I want to get this result

enter image description here

I tried but didn't get the desired result

layer.lineCap = .round
layer.lineJoin = .round

Solution

  • You may need to draw arcs on both sides like:

    let minR: CGFloat = 10
    let bigR: CGFloat = 20
    var path = UIBezierPath()
    
    path.move(to: .init(x: rect.midX - minR, y: minR))
    path.addArc(
        withCenter: .init(x: rect.midX, y: minR),
        radius: minR,
        startAngle: .degrees(0),
        endAngle: .degrees(180),
        clockwise: true
    )
    path.addLine(to: .init(x: rect.midX - bigR, y: rect.maxY - bigR))
    path.addArc(
        withCenter: .init(x: rect.midX, y: rect.maxY - bigR),
        radius: bigR,
        startAngle: .degrees(0),
        endAngle: .degrees(180),
        clockwise: false
    )
    path.move(to: .init(x: rect.midX + bigR, y: rect.maxY - bigR))
    path.addLine(to: .init(x: rect.midX + minR, y: minR))
    path.addLine(to: .init(x: rect.midX - minR, y: minR))
    path.addLine(to: .init(x: rect.midX - bigR, y: rect.maxY - bigR))
    

    Demo

    Don't forget to convert radians to degrees:

    extension CGFloat {
        static func degrees(_ degrees: Double) -> Double { degrees * .pi / 180 }
    }