Creating your own SwiftUI progress spinner

By default SwiftUI gives us ProgressView(), a handy little animation that can indicate to your users that a task - such as a network request - is currently in progress. In Plate-It, I wanted to spice things up a little and create my own spinner, using nothing more than an image and an animation. This tutorial will demonstrate the code that I used to create my simple spinner.

Creating the Spinner#

In your app, you can set the image to whatever you like. In this example, I use an SFSymbol as everyone has access to them, and it works nicely in this example. I made the width of the frame a parameter so that we can override the default value if desired, and then we have a state property isAnimating, which is set to true when the spinner appears and helps to ensure that we can rotate the image.

You can customize the animation to however you like - in this example, the spinner will spin once and then ‘spring’ to make it feel a little more fluid. I recommend reading the Official Apple Documentation about springs to learn more.

struct CustomSpinner: View {
    var frameSize: CGFloat = 72
    @State private var isAnimating = false
    
    var foreverAnimation: Animation {
        Animation
            .spring(response: 1, dampingFraction: 0.7, blendDuration: 0)
            .repeatForever(autoreverses: false)
    }
    
    var body: some View {
        Image(systemName: "person.crop.square")
            .resizable()
            .aspectRatio(contentMode: .fit)
            .frame(width: frameSize)
            .rotationEffect(Angle(degrees: isAnimating ? 360.0 : 0.0))
            .animation(foreverAnimation)
            .onAppear {
                isAnimating = true
            }
    }
}

Once we have the spinner created, we can use this wherever we please in our app. It could be a part of a dedicated loading screen, perhaps you could embed it inside a custom alert, or even inside a custom button. The key thing here is that it’s a reusable, portable component.

struct ContentView: View {
    var body: some View {
        CustomSpinner(frameSize: 90)
            .padding()
    }
}

Conclusion#

If this tutorial helped you in any way, I’d really appreciate a share on Twitter. You can reach out to me via Twitter if you have any questions, or to show me what you’ve been up to!