WWDC22 SALE: Save 50% on all my Swift books and bundles! >>

Creating custom shapes with CoreAnimation/CoreGraphics

Forums > iOS

I'm trying to create a component like:

ticket shape

To do this in code, the only way i know how is to do a CGPath to create this shape.

struct TicketShape {
    let rect: CGRect
    let cornerRadius: CGFloat
    let notchRadius: CGFloat
    let notchOffsetY: CGFloat

    init(rect: CGRect, cornerRadius: CGFloat = 8, notchRadius: CGFloat = 12, notchOffsetY: CGFloat = 0) {
        self.rect = rect
        self.cornerRadius = cornerRadius
        self.notchRadius = notchRadius
        self.notchOffsetY = notchOffsetY
    }

    func makePath() -> CGPath {
        let path = CGMutablePath()
        let notchCenterY = rect.midY + notchOffsetY

        path.move(to: .init(x: cornerRadius, y: 0))

        // from left to right line
        path.addLine(to: .init(x: rect.width - cornerRadius, y: 0))

        // top - right corner
        path.addQuadCurve(
            to: .init(x: rect.width, y: cornerRadius),
            control: .init(x: rect.width, y: 0)
        )

        // top to bottom - right side
        path.addLine(to: .init(x: rect.width, y: notchCenterY - notchRadius))

        // trailing notch
        path.addArc(
            center: .init(x: rect.width, y: notchCenterY),
            radius: notchRadius,
            startAngle: -CGFloat.pi / 2,
            endAngle: CGFloat.pi / 2,
            clockwise: true
        )

        path.addLine(to: .init(x: rect.width, y: rect.height - cornerRadius))

        // bottom right corner
        path.addQuadCurve(
            to: .init(x: rect.width - cornerRadius, y: rect.height),
            control: .init(x: rect.width, y: rect.height)
        )
        path.addLine(to: .init(x: cornerRadius, y: rect.height))

        // bottom left corner
        path.addQuadCurve(
            to: .init(x: 0, y: rect.height - cornerRadius),
            control: .init(x: 0, y: rect.height)
        )
        path.addLine(to: .init(x: 0, y: notchCenterY + notchRadius))

        // leading notch
        path.addArc(
            center: .init(x: 0, y: notchCenterY),
            radius: notchRadius,
            startAngle: CGFloat.pi / 2,
            endAngle: -CGFloat.pi / 2,
            clockwise: true
        )

        path.addLine(to: .init(x: 0, y: cornerRadius))
        path.addQuadCurve(
            to: .init(x: cornerRadius, y: 0),
            control: .init(x: 0, y: 0)
        )

        path.closeSubpath()

        return path
    }

    func makeCAShape() -> CAShapeLayer {
        let shape = CAShapeLayer()
        shape.frame = rect
        shape.path = makePath()
        return shape
    }
}

This code is fairly complicated to understand to achieve such a simple concept. This approach is boils down to draw every line that composes the shape

I was wonderting if there was a different way to achieve this for UIKit.

I wondered if there was a way to "combine shapes" like you do in sketch. Where you create shapes by substracting 2 shapes or more.

In this case to create the ticket shape I would need to:

  1. create a rounded rectangle (A)
  2. create a small circle (B)
  3. remove B from A = C
  4. create a second circle (D)
  5. remove D from C => ticket shape.

As far as i know I cannot just combine layers/shapes in this way.

The other issue is to add this to my UIView class. For that I would do something like:

    override func layoutSubviews() {
        super.layoutSubviews()

        shapeLayer?.fillColor = UIColor.backgroundSecondary.cgColor

        guard shapeLayer == nil else { return }

        let ticketShape = TicketShape(rect: bounds).makeCAShape()
        ticketShape.shadowColor = UIColor.black.cgColor
        ticketShape.shadowRadius = 2
        ticketShape.shadowOffset = CGSize(width: 1, height: 1)
        ticketShape.shadowOpacity = 0.3
        ticketShape.fillColor = UIColor.backgroundSecondary.cgColor

        backgroundView.layer.addSublayer(ticketShape)
        self.shapeLayer = ticketShape
    }

To ensure the color of my shape and the size of it matches the size of the UIView.

I'm basically asking if there are better approaches to do this

1      

Hope you get an answer to your question! I am looking to duplicate the effect of the Blobs in Google's "Blob Opera" have you seen this? It's awesome the way they stretch and wiggle. Love the eye effect and mouth effects.

Looking for a Swift graphics boffin for advice on how to approach this.

I hope you get your answer!

Blob Opera (Google)

1      

Save 50% in my Black Friday sale.

SAVE 50% To celebrate WWDC22, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.