SwiftUI is Apple’s new powerful framework to build applications across all Apple platforms. It is fast, simple, and easy to learn.

It has automatic support for Dark Mode, Dynamic Type, localization, and accessibility.

What we are going to make

We will build this ProgressBar whose progress can be changed by using the Button. It will also have a beautiful animation.

LET’S START

Make sure the canvas is visible. (Shortcut: Command+Shift+Enter)

Creating the Xcode Project

Note: SwiftUI needs Xcode 11 Beta, and for Live Preview to work, you need to be on macOS Catalina. If you have macOS Catalina Beta 4, Live Previews will only work if you also have Xcode 11 Beta 4.

If you don’t have Xcode 11 Beta, you’ll need to run app on the actual Simulator.

Open up Xcode 11 and create a New Xcode Project.

Choose Single View Application under iOS.

Enter whatever you like in the Product Name, and make sure “Use SwiftUI” is checked.

Click Next. Save it on Desktop and click Create.

Creating a static progress bar

SwiftUI apps don’t have Main.storyboard file. All UI you will make will be done programmatically.

Open up ContentView.swift file. This is the initial view of the app. Make sure the Canvas is shown on the right side.

Now, create a new SwiftUI file and name it ProgressBar. Replace Text() with a ZStack.

SwiftUI has 3 types of stacks. VStack (which stacks the views vertically), HStack (which stacks the views horizontally), and ZStack (which stacks the views on top of each other). If you add 2 circles, one with radius 200, and other with radius 100, in a ZStack, they will look like 2 concentric circles as they’re added on top of each other rather then added next to each other.

Inside this ZStack, add 2 Rectangles. One for the progress, and one for the track.

Give the track rectangle the color of gray and opacity of 30%, and the progress rectangle a blue color.

Also, give both the rectangles a fixed frame for now and a corner radius of 4.0 to the ZStack.

ZStack { Rectangle() .foregroundColor(Color.gray) .opacity(0.3) .frame(width: 345.0, height: 8.0) Rectangle() .foregroundColor(Color.blue) .frame(width: 200.0, height: 8.0) } .cornerRadius(4.0)

ProgressBar should look like this:

This is because the alignment of ZStack is set to center by default

Change alignment of ZStack to leading as:

ZStack(alignment: .leading) {

Now it should look more like a progress bar.

Changing Progress of Progress Bar

Open ProgressBar.swift and add a @Binding property called progress of type CGFloat.

Binding is used to create a two-way connection between a view and its underlying model. Read more on Apple Developer Documentation

@Binding var progress: CGFloat

And change the frame of second rectangle according to progress like:

Rectangle() .frame(width: 345.0 * (progress / 100.0), height: 8.0)

Try building and compiler will give you an error:

This is because we haven’t provided progress value for the Live Preview.

Change it to:

ProgressBar(progress: .constant(25.0))

You can create a constant Binding values using .constant(value)

Try changing the value of progress to something else. ProgressBar will fill accordingly.

Now go back to ContentView.swift.

Create a @State variable called progress of type CGFloat.

When the value of State variable changes, the view invalidates its appearance and recomputes the body. Read more on Apple Developer Documentation

@State var progress: CGFloat = 0.0

Next, add a VStack in the body. This VStack will contain the ProgressBar we just created, and a button which will randomly set the progress when tapped.

VStack { ProgressBar(progress: $progress) Button( action: { self.progress = CGFloat.random(in: 0...100) } ) { Text("Random Progress") } }

Now run the app and it should give you a random progress when button is tapped.

Adding Animation when progress is changed

In ProgressBar.swift file, add a @State variable called isShown of type Bool, and set to false initially.

Add .onAppear { } at the end of ZStack. This will be called as soon as the ZStack is appeared. In .onAppear{} set the value of isShown to true.

struct ProgressBar: View { @Binding var progress: CGFloat @State var isShowing = false var body: some View { ZStack(alignment: .leading) { Rectangle() .foregroundColor(Color.gray) .opacity(0.3) .frame(width: 345.0, height: 8.0) Rectangle() .foregroundColor(Color.blue) .frame(width: 345.0 * (self.progress / 100.0), height: 8.0) } .onAppear { self.isShowing = true } .cornerRadius(4.0) } }

Next, change the frame of progress bar to be like:

.frame(width: self.isShown ? 345.0 * (progress / 100.0) : 0, height: 8.0)

It will give the blue progress bar rectangle a width of 0 initially, but as soon as the ZStack appears, it will change its width according to progress provided.

Now adding animation is super easy.

Add this code below the frame line of blue rectangle.

.animation(.linear(duration: 0.6))

This adds a linear animation of duration 0.6 seconds to the rectangle. You can also choose ease-in or ease-out instead of linear.

Try playing with the animation duration and animation type.

Your code should now look like this:

import SwiftUI struct ProgressBar: View { @State var isShowing = false @Binding var progress: CGFloat var body: some View { ZStack(alignment: .leading) { Rectangle() .foregroundColor(Color.gray) .opacity(0.3) .frame(width: 345.0, height: 8.0) Rectangle() .foregroundColor(Color.blue) .frame(width: self.isShowing ? 345.0 * (self.progress / 100.0) : 0.0, height: 8.0) .animation(.linear(duration: 0.6)) } .onAppear { self.isShowing = true } .cornerRadius(4.0) } } #if DEBUG struct ProgressBar_Previews: PreviewProvider { static var previews: some View { ProgressBar(progress: .constant(25.0)) } } #endif

Finally, run the app in Live Preview of ContentView.swift, and press the button. Now the progress should animate beautifully to random value when the button is tapped.

Adding GeometryReader

Here’s a little challenge for you 😉

Try changing the progress bar frame to be dynamic rather than fixed. So that when you use change the frame of ProgressView() in ContentView.swift, its size should change according to set value rather than always being of size 345 (width) by 8 (height).

Hints: 1. You’ll need to use GeometryReader. 2. You can get width using geometry.size.width 3. For some reason, setting the frame of rectangle won’t work if you add the frame line only once. Add it 2 times. It’s a bug in Xcode 11 Beta 4.

Finished source is available on my GitHub repository.

Thanks for reading!