Loading Animation in SwiftUI

How to create a custom loading animation in SwiftUI

Ix76y
8 min readDec 29, 2022

--

Hey everyone, my name is Izzy and in this short article I want to show you how you can create a custom loading screen. Instead of just using the default loading circle I want to show you a very easy way of creating a cool loading animation without a lot of effort.

Final Result

This is what we will be creating:

Triangle rotating as loading animation
Triangle Loading Animation

Prerequisites

  • Mac with Xcode 13+ installed
  • Experience in Swift programming is not necessary but can make your life easier

Create a New Project

Open up Xcode and create a new App project or use an existing one. If you are new to Xcode and app development you can follow the detailed steps I made in one of my other articles, like “Creating an Image Card in SwiftUI”. This article also contains a small section about the interface of Xcode to get you started.

Creating a Triangle Shape

SwiftUI comes with a few predefined shapes. These are shapes like a circle, capsule, or rectangle. However, a triangle is sadly not part of it so we need to create this on our own. Luckily triangles have a super easy shape with only three points so the code for this is rather simple.

There are two ways how you can get a triangle into your app:

  1. As a picture
  2. As a custom shape

Using a picture is super easy as we can just add what ever shape we want to our asset folder but creating a custom shape allows us to change the shape easier and apply modifiers like color or adding a border.

How Shapes Work in SwiftUI

Before we can create a custom triangle shape we need to understand how shapes, or even more general, views work in SwiftUI. Everything that you can see in an app is a view, and part of that are shapes. Every view is taking up as much space as it can possible get.

With that in mind, when we create our triangle we will have the information of the space we have for our triangle. We want the triangle to span over the top two corners and the third point should be in the middle of the bottom line. The drawing below shows exactly that.

Drawing a triangle spanning the full area
Drawing a Triangle in SwiftUI

Creating a Custom Shape

We know now that we want to create a new shape with three points which should be connected. Let’s start by creating a new struct which is going to be a subclass from Shape:

struct Triangle: Shape {

}

In our new struct we only need to implement one method: path(). Path() has one argument, which is a rectangle which defines the space that this shape will have.

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

}
}

A CGRect has all the information we need to draw our triangle:

  • rect.minX and rect.minY returns the smallest values for X and Y
  • rect.maxX and rect.maxY returns the biggest values
  • and rect.midX and rect.midX is the middle between the smallest and largest

As you can see from the definition of the path() method it returns a path object, which is what we need to create. Then we tell the path

  1. where to start: in our case we will use the top left corner at minX & minY
  2. where to move to next: in that case we want to move to the right to our next point of the triangle which is at maxX and minY
  3. where to move to next: after the top right corner we are going to move to the tip of the triangle to the bottom middle, at midX and maxY
  4. finally we need to tell the path to close itself

In code this looks as follows:

struct Triangle: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: rect.minX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY))
path.closeSubpath()
return path
}
}

Now that we have our triangle shape defined let’s use it in our actual layout.

Setting up the Main Layout

For the layout we want to have two triangles, one bigger and the other one a bit small in the middle of the first and below each of them a text.

Layout of loading screen with two triangles above text
Layout of Loading Screen

We can achieve this layout by first adding a VStack. A VStack puts all elements inside it underneath each other. The VStack will contain a ZStack and a text underneath the ZStack. A ZStack put views on top of each other. In the ZStack we will have the two triangles with the smaller triangle on top of the larger one.

Layout Sketch with VStack containing ZStack and Text
Layout Sketch
var body: some View {
VStack {
ZStack {
Triangle()
Triangle()
}
Text("Loading...")
}
}

After adding all that into the body of our main view it will probably not look directly the way we want it to look. We need to adjust our triangles a bit.

First, lets give them a border color. This is super easy with shapes: just add the border() modifier and pass the color you want the border to have. Additionally you can pass a line width, which we can save in a variable to change the width for both triangles at the same time.

private let borderWidth = 6.0

var body: some View {
VStack {
ZStack {
Triangle()
.stroke(.purple, lineWidth: borderWidth)
Triangle()
.stroke(.pink, lineWidth: borderWidth)
}
Text("Loading...")
}
}

This already looks better but currently the triangles overlay each other so we can’t see the bottom one. We can solve this by first give each of them a frame to generally adjust their size and give the second one a smaller one.

private let borderWidth = 6.0
private var let sizeBig = 200.0
private var let sizeSmall = 86.0

var body: some View {
VStack {
ZStack {
Triangle()
.stroke(.orange, lineWidth: borderWidth)
// make the triangle a little bit wider and high, by multiplying the height times 0.8
.frame(width: sizeBig, height: sizeBig * 0.8)
Triangle()
.stroke(.yellow, lineWidth: borderWidth)
.frame(width: sizeSmall, height: sizeSmall * 0.8)
}
Text("Loading...")
}
}

The size can be again adjusted with variables that way we can easily change the values if we decide that we want to have smaller or bigger triangles.

To finalize the design we want to turn the smaller triangle 180° and offset its position a bit.

private let borderWidth = 6.0
private var let sizeBig = 200.0
private var let sizeSmall = 86.0

private var rotationSmall = 180.0 // rotation for smaller triangle: 180°

var body: some View {
VStack {
ZStack {
Triangle()
.stroke(Color("Triangle1"), lineWidth: borderWidth)
.frame(width: size, height: size * 0.8)
Triangle()
.stroke(Color("Triangle2"), lineWidth: borderWidth)
.frame(width: small, height: small * 0.8)
.offset(y: small * 0.2) // move the small triangle a bit up
.rotationEffect(Angle(degrees: rotationSmall)) // apply rotation
}
Text("Loading...")
}
}

After this the only thing that is left is the actual rotation animation.

Animating the Triangle

Animations in SwiftUI are very easy to implement and straight forward. There are three things we need:

  1. A state variable to define if we want to play the animation or not
  2. A modifier on a view that uses the above defined variable
  3. A function to create the animation and change the value of the variable from step 1

Let’s start with creating a variable, with a @State property.

private let size = 200.0
private let small = 86.0
private let borderWidth = 6.0
private var rotationSmall = 180.0

// at the start it shoudln't rotate, so the default is false
@State private var loading = false

Once we declare a variable with @State each view that is using it will update itself once that value changes. So once we set loading to true the big triangle should start to rotate. Similar to the small triangle we can add a rotationEffect to the bigger triangle that uses the loading variable:

private let borderWidth = 6.0
private var let sizeBig = 200.0
private var let sizeSmall = 86.0
private var rotationSmall = 180.0

@State private var loading = false

var body: some View {
VStack {
ZStack {
Triangle()
.stroke(Color("Triangle1"), lineWidth: borderWidth)
.frame(width: size, height: size * 0.8)
// using the ?: operator we can say:
// if loading is true set the rotation to 360°
// else set it to 0°
.rotationEffect(Angle(degrees: loading ? 360 : 0))
Triangle()
.stroke(Color("Triangle2"), lineWidth: borderWidth)
.frame(width: small, height: small * 0.8)
.offset(y: small * 0.2)
.rotationEffect(Angle(degrees: rotationSmall))
}
Text("Loading...")
}
}

Now we only need to have a trigger for the animation. This could be a button or in this case we will just trigger it once the view appears.

We will use a linear animation, that way the triangle will always turn with the same speed. Additionally we want to set repeatForever() so that the animation just starts at the start again after one round. If the triangle is turning to quickly for your taste you can adjust its speed by passing a small value to the speed() method.

private let borderWidth = 6.0
private var let sizeBig = 200.0
private var let sizeSmall = 86.0
private var rotationSmall = 180.0

@State private var loading = false

var body: some View {
VStack {
ZStack {
Triangle()
.stroke(Color("Triangle1"), lineWidth: borderWidth)
.frame(width: size, height: size * 0.8)
.rotationEffect(Angle(degrees: loading ? 360 : 0))
Triangle()
.stroke(Color("Triangle2"), lineWidth: borderWidth)
.frame(width: small, height: small * 0.8)
.offset(y: small * 0.2)
.rotationEffect(Angle(degrees: rotationSmall))
}
Text("Loading...")
}.onAppear(perform: { // when the view appears do the following
withAnimation( // create an animation
.linear // that runs linear
.speed(0.1) // slow the animation
.repeatForever(autoreverses: false)) { // and set it to repeat
loading = true // set loading to true once the view appears which then triggers the animation
}
})
}

Congratulations, you should now have your own custom loading animation! 🥳

If you want to learn how to add a custom font to your Swift UI Project check out this article: Tutorial — How to add a Custom Font in SwiftUI.

Thank you ❤️

Thank you for reading, I hope this was helpful! If you have any questions or suggestions, let me know in the comments.

And if you did enjoy this story please follow me for more stories! 😊

--

--

Ix76y
Ix76y

Written by Ix76y

Hi my name is Izzy. I love to write code, currently my favorits are Python, Swift, Rust & C and I enjoy writing tutorials about all sorts of things 😊.

No responses yet