In SwiftUI, providing users with clear feedback on ongoing processes is crucial for a good user experience. A custom progress bar can be tailored to fit the design and functionality of your app perfectly. This blog post will guide you through creating a customizable progress bar in SwiftUI from scratch.
What is a Custom Progress Bar?
A progress bar is a visual element used to indicate the progression of a task to the user. Unlike the default progress indicators, a custom progress bar allows you to control every aspect of its appearance, from color and shape to animations and text overlays.
Why Build a Custom Progress Bar?
- Brand Consistency: Tailor the look and feel to match your app’s design.
- Advanced Features: Add custom animations, labels, and interactions.
- Flexibility: Customize behavior and appearance based on specific app requirements.
How to Build a Custom Progress Bar in SwiftUI
Follow these steps to create a customizable progress bar in SwiftUI:
Step 1: Define the Basic Structure
Start by creating a basic structure using GeometryReader, ZStack, and Rectangle shapes:
import SwiftUI
struct CustomProgressBar: View {
@Binding var progress: CGFloat
var color: Color = Color.blue
var height: CGFloat = 20
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .leading) {
Rectangle()
.frame(width: geometry.size.width, height: height)
.opacity(0.3)
.foregroundColor(color)
Rectangle()
.frame(width: min(CGFloat(progress) * geometry.size.width, geometry.size.width), height: height)
.foregroundColor(color)
.animation(.linear, value: progress) // Smooth animation
}
}.frame(height: height)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct ContentView: View {
@State private var progress: CGFloat = 0.5
var body: some View {
VStack {
CustomProgressBar(progress: $progress, color: .green, height: 30)
.padding()
Slider(value: $progress, in: 0...1, step: 0.01)
.padding()
Text("Progress: ((progress * 100).formatted(.number.precision(.fractionLength(0))))%")
.padding()
}
}
}
In this example:
@Binding var progress: CGFloat: Binds the progress value to a variable outside the view, allowing external control.color: Color = Color.blueandheight: CGFloat = 20: Defines customizable color and height parameters.GeometryReader: Allows the progress bar to dynamically adapt to its container’s size.ZStack: Layers a base rectangle with reduced opacity and a colored rectangle representing the progress.animation(.linear, value: progress): Animates the progress bar’s changes smoothly.
Step 2: Add Customizable Parameters
Enhance the progress bar by adding more customizable parameters, such as corner radius and gradient colors.
import SwiftUI
struct CustomProgressBar: View {
@Binding var progress: CGFloat
var startColor: Color = Color.blue
var endColor: Color = Color.blue
var height: CGFloat = 20
var cornerRadius: CGFloat = 10
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: cornerRadius)
.frame(width: geometry.size.width, height: height)
.opacity(0.3)
.foregroundColor(startColor)
RoundedRectangle(cornerRadius: cornerRadius)
.fill(
LinearGradient(
gradient: Gradient(colors: [startColor, endColor]),
startPoint: .leading,
endPoint: .trailing
)
)
.frame(width: min(CGFloat(progress) * geometry.size.width, geometry.size.width), height: height)
.animation(.linear, value: progress)
}
}
.frame(height: height)
}
}
struct ContentView: View {
@State private var progress: CGFloat = 0.6
var body: some View {
VStack {
CustomProgressBar(progress: $progress, startColor: .orange, endColor: .red, height: 30, cornerRadius: 15)
.padding()
Slider(value: $progress, in: 0...1, step: 0.01)
.padding()
Text("Progress: ((progress * 100).formatted(.number.precision(.fractionLength(0))))%")
.padding()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Key enhancements:
startColorandendColor: Defines gradient colors for a visually appealing progress bar.cornerRadius: Makes the progress bar rounded for a softer look.LinearGradient: Applies a gradient to the progress bar.- Usage in
ContentViewto showcase the customizable options.
Step 3: Add a Label and Animation
Enhance the user experience by adding a progress label and animating the progress bar. Update the CustomProgressBar to add an animated overlay with the progress value:
import SwiftUI
struct CustomProgressBar: View {
@Binding var progress: CGFloat
var startColor: Color = Color.blue
var endColor: Color = Color.blue
var height: CGFloat = 20
var cornerRadius: CGFloat = 10
var showLabel: Bool = true
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: cornerRadius)
.frame(width: geometry.size.width, height: height)
.opacity(0.3)
.foregroundColor(startColor)
RoundedRectangle(cornerRadius: cornerRadius)
.fill(
LinearGradient(
gradient: Gradient(colors: [startColor, endColor]),
startPoint: .leading,
endPoint: .trailing
)
)
.frame(width: min(CGFloat(progress) * geometry.size.width, geometry.size.width), height: height)
.animation(.linear, value: progress)
}
if showLabel {
Text("((progress * 100).formatted(.number.precision(.fractionLength(0))))%")
.font(.caption)
.foregroundColor(.white)
.padding(5)
.frame(width: geometry.size.width, alignment: .center)
.transition(.opacity)
}
}
.frame(height: height)
}
}
struct ContentView: View {
@State private var progress: CGFloat = 0.7
var body: some View {
VStack {
CustomProgressBar(progress: $progress, startColor: .green, endColor: .blue, height: 30, cornerRadius: 15, showLabel: true)
.padding()
Slider(value: $progress, in: 0...1, step: 0.01)
.padding()
Text("Progress: ((progress * 100).formatted(.number.precision(.fractionLength(0))))%")
.padding()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
New additions include:
showLabel: Determines whether to show the progress percentage label over the bar.- Text overlay with animation, displaying the percentage of progress on the progress bar
Complete Code
Below is the final code integrating all of the enhancements
import SwiftUI
struct CustomProgressBar: View {
@Binding var progress: CGFloat
var startColor: Color = Color.blue
var endColor: Color = Color.blue
var height: CGFloat = 20
var cornerRadius: CGFloat = 10
var showLabel: Bool = true
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: cornerRadius)
.frame(width: geometry.size.width, height: height)
.opacity(0.3)
.foregroundColor(startColor)
RoundedRectangle(cornerRadius: cornerRadius)
.fill(
LinearGradient(
gradient: Gradient(colors: [startColor, endColor]),
startPoint: .leading,
endPoint: .trailing
)
)
.frame(width: min(CGFloat(progress) * geometry.size.width, geometry.size.width), height: height)
.animation(.linear, value: progress)
if showLabel {
Text("((progress * 100).formatted(.number.precision(.fractionLength(0))))%")
.font(.caption)
.foregroundColor(.white)
.padding(5)
.frame(width: geometry.size.width, alignment: .center)
.transition(.opacity)
}
}
}
.frame(height: height)
}
}
struct ContentView: View {
@State private var progress: CGFloat = 0.7
var body: some View {
VStack {
CustomProgressBar(progress: $progress, startColor: .green, endColor: .blue, height: 30, cornerRadius: 15, showLabel: true)
.padding()
Slider(value: $progress, in: 0...1, step: 0.01)
.padding()
Text("Progress: ((progress * 100).formatted(.number.precision(.fractionLength(0))))%")
.padding()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Conclusion
Building a custom progress bar in SwiftUI offers a powerful way to tailor your app’s UI to your exact needs. With the ability to customize colors, shapes, labels, and animations, you can create a progress bar that enhances user experience and reinforces your brand identity. Experiment with different styles and parameters to find the perfect fit for your application.