Skip Navigation
Drawing and animation

Drawing paths and shapes

Users receive a badge whenever they visit a landmark in their list. Of course, for a user to receive a badge, you’ll need to create one. This tutorial takes you through the process of creating a badge by combining paths and shapes, which you then overlay with another shape that represents the location.

If you want to create multiple badges for different kinds of landmarks, try experimenting with the overlaid symbol, varying the amount of repetition, or changing the various angles and scales.

Follow the steps to build this project, or download the finished project to explore on your own.

Section 1

Create drawing data for a badge view

To create the badge, you’ll start by defining data that you can use to draw a hexagon shape for the badge’s background.

An illustration of the badge you will create in this tutorial. The badge has a hexagonal shape, using a single graphic to create an icon in the center that makes an eight-pointed star pattern. The hexagon has a vertical gradient.

Step 1

With the Views group selected in the navigation pane, choose File > New > File, select Swift File from the iOS Templates sheet, and click Next.

A screenshot of Xcode's iOS template sheet with Swift File selected.

Step 2

Name the new file HexagonParameters.swift.

You’ll use this structure to define the shape of a hexagon.

A screenshot of the Xcode dialog for adding a new file showing HexagonParameters.swift as the name of the file to be added.

Step 3

Inside the new file, create a structure called HexagonParameters.

HexagonParameters.swift
import Foundation


struct HexagonParameters {
}
No Preview

Step 4

Define a Segment structure to hold the three points that represent one side of the hexagon; import CoreGraphics so you can use CGPoint.

Each side starts where the previous ends, moves in a straight line to the first point, and then moves over a Bézier curve at the corner to the second point. The third point controls the shape of the curve.

HexagonParameters.swift
import CoreGraphics


struct HexagonParameters {
No Preview

Step 5

Create an array to hold segments.

HexagonParameters.swift
        let control: CGPoint
    }


    static let segments = [
    ]
}
No Preview

Step 6

Add data for the six segments, one for each side of the hexagon.

The values are stored as a fraction of a unit square having its origin in the upper left, with positive x to the right and positive y down. Later, you’ll use these fractions to find the actual points of a hexagon with a given size.

HexagonParameters.swift


    static let segments = [
        Segment(
            line:    CGPoint(x: 0.60, y: 0.05),
            curve:   CGPoint(x: 0.40, y: 0.05),
            control: CGPoint(x: 0.50, y: 0.00)
        ),
        Segment(
            line:    CGPoint(x: 0.05, y: 0.20),
            curve:   CGPoint(x: 0.00, y: 0.30),
            control: CGPoint(x: 0.00, y: 0.25)
        ),
        Segment(
            line:    CGPoint(x: 0.00, y: 0.70),
            curve:   CGPoint(x: 0.05, y: 0.80),
            control: CGPoint(x: 0.00, y: 0.75)
        ),
        Segment(
            line:    CGPoint(x: 0.40, y: 0.95),
            curve:   CGPoint(x: 0.60, y: 0.95),
            control: CGPoint(x: 0.50, y: 1.00)
        ),
        Segment(
            line:    CGPoint(x: 0.95, y: 0.80),
            curve:   CGPoint(x: 1.00, y: 0.70),
            control: CGPoint(x: 1.00, y: 0.75)
        ),
        Segment(
            line:    CGPoint(x: 1.00, y: 0.30),
            curve:   CGPoint(x: 0.95, y: 0.20),
            control: CGPoint(x: 1.00, y: 0.25)
        )
    ]
}
No Preview

Step 7

Add an adjustment value that lets you tune the shape of the hexagon.

HexagonParameters.swift
    }


    static let adjustment: CGFloat = 0.085


    static let segments = [
        Segment(
No Preview
A screenshot of Xcode's iOS template sheet with Swift File selected.
Section 2

Draw the badge background

Use the graphics APIs in SwiftUI to draw a custom badge shape.

An illustration of a square that has a graph-paper background to create a 25 by 25 grid. The square is divided into four quadrants, as indicated by a horizontal and vertical dashed line. A hexagonal shape for the badge is placed at the center of the grid. The hexagon has rounded corners and has a vertical gradient fill.

Step 1

Create another new file with File > New > File, this time selecting SwiftUI View from the iOS Templates sheet. Click Next and then name the file BadgeBackground.swift.

A screenshot of the Templates sheet in Xcode. In the top row, iOS is selected as the platform, and SwiftUI View is selected under the User Interface section.

Step 2

In BadgeBackground, add a Path shape to the badge and apply the fill() modifier to turn the shape into a view.

You use paths to combine lines, curves, and other drawing primitives to form more complex shapes like the badge’s hexagonal background.

BadgeBackground.swift
struct BadgeBackground: View {
    var body: some View {
        Path { path in


        }
        .fill(.black)
    }
}
No Preview

Step 3

Add a starting point to the path, assuming a container with size 100 x 100 px.

The move(to:) method moves the drawing cursor within the bounds of a shape as though an imaginary pen or pencil is hovering over the area, waiting to start drawing.

BadgeBackground.swift
    var body: some View {
        Path { path in
            var width: CGFloat = 100.0
            let height = width
            path.move(
                to: CGPoint(
                    x: width * 0.95,
                    y: height * 0.20
                )
            )
        }
        .fill(.black)
No Preview

Step 4

Draw the lines for each point of the shape data to create a rough hexagonal shape.

The addLine(to:) method takes a single point and draws it. Successive calls to addLine(to:) begin a line at the previous point and continue to the new point.

BadgeBackground.swift
                )
            )


            HexagonParameters.segments.forEach { segment in
                path.addLine(
                    to: CGPoint(
                        x: width * segment.line.x,
                        y: height * segment.line.y
                    )
                )
            }
        }
        .fill(.black)

Don’t worry if your hexagon looks a little unusual; that’s because you’re ignoring the curved part of each segment at the shape’s corners. You’ll account for that next.

Step 5

Use the addQuadCurve(to:control:) method to draw the Bézier curves for the badge’s corners.

BadgeBackground.swift
                to: CGPoint(
                    x: width * 0.95,
                    y: height * (0.20 + HexagonParameters.adjustment)
                )
            )

Step 6

Wrap the path in a GeometryReader so the badge can use the size of its containing view, which defines the size instead of hard-coding the value (100).

Using the smallest of the geometry’s two dimensions preserves the aspect ratio of the badge when its containing view isn’t square.

BadgeBackground.swift
struct BadgeBackground: View {
    var body: some View {
        GeometryReader { geometry in
            Path { path in
                var width: CGFloat = min(geometry.size.width, geometry.size.height)

Step 7

Scale the shape on the x-axis using xScale, and then add xOffset to recenter the shape within its geometry.

BadgeBackground.swift
                var width: CGFloat = min(geometry.size.width, geometry.size.height)
                let height = width
                let xScale: CGFloat = 0.832
                let xOffset = (width * (1.0 - xScale)) / 2.0
                width *= xScale
                path.move(
                    to: CGPoint(

Step 8

Replace the solid black background with a gradient to match the design.

BadgeBackground.swift
                }
            }
            .fill(.linearGradient(
                Gradient(colors: [Self.gradientStart, Self.gradientEnd]),
                startPoint: UnitPoint(x: 0.5, y: 0),
                endPoint: UnitPoint(x: 0.5, y: 0.6)
            ))
        }
    }

Step 9

Apply the aspectRatio(_:contentMode:) modifier to the gradient fill.

By preserving a 1:1 aspect ratio, the badge maintains its position at the center of the view, even if its ancestor views aren’t square.

BadgeBackground.swift
            ))
        }
        .aspectRatio(1, contentMode: .fit)
    }
    static let gradientStart = Color(red: 239.0 / 255, green: 120.0 / 255, blue: 221.0 / 255)
A screenshot of the Templates sheet in Xcode. In the top row, iOS is selected as the platform, and SwiftUI View is selected under the User Interface section.
Section 3

Draw the badge symbol

The Landmarks badge has a custom insignia in its center that’s based on the mountain that appears in the Landmarks app icon.

The mountain symbol consists of two shapes: one that represents a snowcap at the peak, and the other that represents vegetation along the approach. You’ll draw them using two partially triangular shapes that are set apart by a small gap.

A screenshot showing a shape of a mountain on the left, and the app icon for the Landmarks app on the right.

First you’ll give your app an icon, to establish a look for the badge.

Step 1

Navigate to the empty AppIcon item from your project’s Asset Catalog, and then drag the single png file from the downloaded projects’ Resources folder into the existing empty AppIcon set.

A screenshot of Xcode showing the app icon in the asset catalog.

Next, you’ll build the matching badge symbol.

Step 2

Create a new custom view called BadgeSymbol for the mountain shape that’s stamped in a rotated pattern in the badge design.

BadgeSymbol.swift
import SwiftUI


struct BadgeSymbol: View {
    var body: some View {
        Text("Hello, World!")
    }
}


#Preview {
    BadgeSymbol()
}
No Preview

Step 3

Draw the top portion of the symbol using the path APIs.

BadgeSymbol.swift
struct BadgeSymbol: View {
    var body: some View {
        GeometryReader { geometry in
            Path { path in
                let width = min(geometry.size.width, geometry.size.height)
                let height = width * 0.75
                let spacing = width * 0.030
                let middle = width * 0.5
                let topWidth = width * 0.226
                let topHeight = height * 0.488


                path.addLines([
                    CGPoint(x: middle, y: spacing),
                    CGPoint(x: middle - topWidth, y: topHeight - spacing),
                    CGPoint(x: middle, y: topHeight / 2 + spacing),
                    CGPoint(x: middle + topWidth, y: topHeight - spacing),
                    CGPoint(x: middle, y: spacing)
                ])
            }
        }
    }
}

Step 4

Draw the bottom portion of the symbol.

Use the move(to:) modifier to insert a gap between multiple shapes in the same path.

BadgeSymbol.swift
                    CGPoint(x: middle, y: spacing)
                ])
                
                path.move(to: CGPoint(x: middle, y: topHeight / 2 + spacing * 3))
                path.addLines([
                    CGPoint(x: middle - topWidth, y: topHeight + spacing),
                    CGPoint(x: spacing, y: height - spacing),
                    CGPoint(x: width - spacing, y: height - spacing),
                    CGPoint(x: middle + topWidth, y: topHeight + spacing),
                    CGPoint(x: middle, y: topHeight / 2 + spacing * 3)
                ])
            }
        }

Step 5

Fill the symbol with the purple color from the design.

BadgeSymbol.swift


struct BadgeSymbol: View {
    static let symbolColor = Color(red: 79.0 / 255, green: 79.0 / 255, blue: 191.0 / 255)


    var body: some View {
        GeometryReader { geometry in

Step 6

Create a new RotatedBadgeSymbol view to encapsulate the concept of a rotated symbol.

RotatedBadgeSymbol.swift
import SwiftUI


struct RotatedBadgeSymbol: View {
    let angle: Angle
    
    var body: some View {
        BadgeSymbol()
            .padding(-60)
            .rotationEffect(angle, anchor: .bottom)
    }
}


#Preview {
    RotatedBadgeSymbol(angle: Angle(degrees: 5))
}
A screenshot of Xcode showing the app icon in the asset catalog.
Section 4

Combine the badge foreground and background

The badge design calls for the mountain shape to be rotated and repeated multiple times on top of the badge background.

Define a new type for rotation and leverage the ForEach view to apply the same adjustments to multiple copies of the mountain shape.

A diagram that shows the finished badge. In the top-left corner is the base shape -- the mountain shape you just created. In the bottom-left corner is an image that shows how the base shape is rotated and drawn to create the icon at the badge's center.

Step 1

Create a new SwiftUI view called Badge.

Badge.swift
import SwiftUI


struct Badge: View {
    var body: some View {
        Text("Hello, World!")
    }
}


#Preview {
    Badge()
}
No Preview

Step 2

Place BadgeBackground in the body of Badge.

Badge.swift
struct Badge: View {
    var body: some View {
        BadgeBackground()
    }
}

Step 3

Lay the badge’s symbol over the badge background by placing it in a ZStack.

Badge.swift


struct Badge: View {
    var badgeSymbols: some View {
        RotatedBadgeSymbol(angle: Angle(degrees: 0))
            .opacity(0.5)
    }
    
    var body: some View {
        ZStack {

As it appears now, the badge symbol is too large compared to the intended design and relative size of the background.

Step 4

Correct the size of the badge symbol by reading the surrounding geometry and scaling the symbol.

Badge.swift
            BadgeBackground()
            
            GeometryReader { geometry in
                badgeSymbols
                    .scaleEffect(1.0 / 4.0, anchor: .top)
                    .position(x: geometry.size.width / 2.0, y: (3.0 / 4.0) * geometry.size.height)
            }
        }
    }

Step 5

Add a ForEach view to rotate and display copies of the badge symbol.

A full, 360° rotation split into eight segments creates a sun-like pattern by repeating the mountain symbol.

Badge.swift
struct Badge: View {
    var badgeSymbols: some View {
        ForEach(0..<8) { index in
            RotatedBadgeSymbol(
                angle: .degrees(Double(index) / Double(8)) * 360.0
            )
        }
        .opacity(0.5)
    }
    

Step 6

To keep the project organized, before moving on to the next tutorial, collect all of the new files that you added in this tutorial into a Badges group.

A screenshot of the Xcode navigator pane showing all the new files collected into a group called Badges.
Badge.swift
import SwiftUI


struct Badge: View {
    var body: some View {
        Text("Hello, World!")
    }
}


#Preview {
    Badge()
}

Question 1 of 3

What is the purpose of GeometryReader?

Possible answers
Next

Animating views and transitions

When using SwiftUI, you can individually animate changes to views, or to a view’s state, no matter where the effects are. SwiftUI handles all the complexity of these combined, overlapping, and interruptible animations for you.

Get started
A diagram that shows a wireframe of some views.