Creating custom shapes in SwiftUI allows you to draw unique graphics and build creative user interfaces. By conforming to the Shape protocol, you can create reusable, scalable vector graphics that integrate seamlessly with SwiftUI's rendering system.

What are Custom Shapes?

Custom shapes in SwiftUI are types that conform to the Shape protocol. They define a path that SwiftUI can fill, stroke, or use for clipping. Shapes are resolution-independent and scale beautifully.

Creating a Basic Custom Shape

Here's how to create a simple triangle shape:

import SwiftUI

struct Triangle: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()

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

        return path
    }
}

struct ContentView: View {
    var body: some View {
        Triangle()
            .fill(Color.blue)
            .frame(width: 200, height: 200)
    }
}

Advanced Custom Shape: Star

Here's a more complex example creating a star shape:

struct Star: Shape {
    let points: Int
    let smoothness: Double

    func path(in rect: CGRect) -> Path {
        let center = CGPoint(x: rect.width / 2, y: rect.height / 2)
        let outerRadius = min(rect.width, rect.height) / 2
        let innerRadius = outerRadius * smoothness
        var angle: Double = -.pi / 2
        let angleIncrement = .pi * 2 / Double(points * 2)

        var path = Path()

        for point in 0..<points * 2 {
            let radius = point.isMultiple(of: 2) ? outerRadius : innerRadius
            let x = center.x + CGFloat(cos(angle)) * radius
            let y = center.y + CGFloat(sin(angle)) * radius

            if point == 0 {
                path.move(to: CGPoint(x: x, y: y))
            } else {
                path.addLine(to: CGPoint(x: x, y: y))
            }

            angle += angleIncrement
        }

        path.closeSubpath()
        return path
    }
}

struct StarView: View {
    var body: some View {
        Star(points: 5, smoothness: 0.45)
            .fill(Color.yellow)
            .frame(width: 200, height: 200)
    }
}

Animatable Shapes

You can make shapes animatable by conforming to Animatable:

struct AnimatableStar: Shape {
    var points: Double

    var animatableData: Double {
        get { points }
        set { points = newValue }
    }

    func path(in rect: CGRect) -> Path {
        Star(points: Int(points), smoothness: 0.45).path(in: rect)
    }
}

struct AnimatedStarView: View {
    @State private var points: Double = 5

    var body: some View {
        VStack {
            AnimatableStar(points: points)
                .fill(Color.blue)
                .frame(width: 200, height: 200)
                .animation(.easeInOut, value: points)

            Slider(value: $points, in: 3...10, step: 1)
        }
        .padding()
    }
}

Use Cases

Custom shapes are perfect for:

  • Creating unique UI elements

  • Building custom charts and graphs

  • Designing logos and icons

  • Creating progress indicators

  • Building custom buttons and controls

Wrap up

The Shape protocol is straightforward — implement path(in:) and SwiftUI handles the rest. Custom shapes scale infinitely and work with any fill, stroke, or trim modifier.

Resources:

Read more

Share


Share Bluesky Mastodon Twitter LinkedIn Facebook