← Back to Home

Creating Custom Progress Bars in SwiftUI

SwiftUI UI Design iOS

Progress bars are essential UI elements that provide visual feedback about ongoing processes. While SwiftUI provides a basic ProgressView, creating custom progress bars can enhance your app's visual appeal and user experience. In this post, we'll explore different ways to implement progress bars in SwiftUI.

1. Basic Linear Progress Bar

Let's start with a simple custom linear progress bar:

CustomProgressBar.swift
struct CustomProgressBar: View {
    let progress: Double
    
    var body: some View {
        GeometryReader { geometry in
            ZStack(alignment: .leading) {
                Rectangle()
                    .foregroundColor(.gray.opacity(0.3))
                
                Rectangle()
                    .foregroundColor(.blue)
                    .frame(width: min(CGFloat(self.progress) * geometry.size.width, 
                                   geometry.size.width))
            }
        }
        .cornerRadius(8)
        .frame(height: 8)
    }
}

// Usage Example
struct ContentView: View {
    @State private var progress = 0.0
    
    var body: some View {
        VStack {
            CustomProgressBar(progress: progress)
                .padding()
            
            Button("Increment") {
                withAnimation {
                    progress = min(1.0, progress + 0.2)
                }
            }
        }
    }
}

2. Animated Circular Progress Bar

For a more dynamic look, here's a circular progress indicator:

CircularProgressBar.swift
struct CircularProgressBar: View {
    let progress: Double
    let lineWidth: CGFloat = 10
    
    var body: some View {
        ZStack {
            Circle()
                .stroke(
                    Color.gray.opacity(0.3),
                    lineWidth: lineWidth
                )
            
            Circle()
                .trim(from: 0, to: CGFloat(progress))
                .stroke(
                    Color.blue,
                    style: StrokeStyle(
                        lineWidth: lineWidth,
                        lineCap: .round
                    )
                )
                .rotationEffect(.degrees(-90))
                // Optional: Add animation
                .animation(.easeInOut, value: progress)
        }
    }
}

// Usage with percentage label
struct CircularProgressView: View {
    @State private var progress = 0.0
    
    var body: some View {
        ZStack {
            CircularProgressBar(progress: progress)
                .frame(width: 100, height: 100)
            
            Text("\(Int(progress * 100))%")
                .font(.system(.title3, design: .rounded))
                .bold()
        }
        .onAppear {
            withAnimation(.easeInOut(duration: 2)) {
                progress = 0.75
            }
        }
    }
}

3. Progress Bar with Gradient

Add some style with gradients:

GradientProgressBar.swift
struct GradientProgressBar: View {
    let progress: Double
    let gradient = LinearGradient(
        colors: [.blue, .purple],
        startPoint: .leading,
        endPoint: .trailing
    )
    
    var body: some View {
        GeometryReader { geometry in
            ZStack(alignment: .leading) {
                Rectangle()
                    .foregroundColor(.gray.opacity(0.3))
                
                Rectangle()
                    .fill(gradient)
                    .frame(width: min(CGFloat(self.progress) * geometry.size.width, 
                                   geometry.size.width))
            }
        }
        .cornerRadius(8)
        .frame(height: 8)
    }
}

4. Indeterminate Progress Bar

For scenarios where progress can't be determined:

IndeterminateProgressBar.swift
struct IndeterminateProgressBar: View {
    @State private var isAnimating = false
    
    var body: some View {
        GeometryReader { geometry in
            ZStack(alignment: .leading) {
                Rectangle()
                    .foregroundColor(.gray.opacity(0.3))
                
                Rectangle()
                    .foregroundColor(.blue)
                    .frame(width: geometry.size.width * 0.3)
                    .offset(x: isAnimating ? geometry.size.width : -geometry.size.width * 0.3)
            }
        }
        .cornerRadius(8)
        .frame(height: 8)
        .onAppear {
            withAnimation(
                .linear(duration: 1)
                .repeatForever(autoreverses: false)
            ) {
                isAnimating = true
            }
        }
    }
}

Best Practices

  • Always animate progress changes for smooth transitions
  • Use appropriate colors that match your app's theme
  • Consider accessibility - provide alternative text for screen readers
  • Maintain consistent sizing across your app

Accessibility Enhancement

Make your progress bars accessible:

AccessibleProgressBar.swift
struct AccessibleProgressBar: View {
    let progress: Double
    let label: String
    
    var body: some View {
        CustomProgressBar(progress: progress)
            .accessibilityValue("\(Int(progress * 100)) percent complete")
            .accessibilityLabel(label)
            .accessibilityProgressLabels("0 percent", "100 percent")
    }
}

These progress bar implementations provide a solid foundation for your SwiftUI apps. Remember to consider your specific use case when choosing between different styles and animations. The key is to provide clear visual feedback while maintaining good performance.

Conclusion

Custom progress bars in SwiftUI offer great flexibility in design while maintaining smooth animations and good performance. Whether you need a simple linear progress bar or a more complex animated indicator, SwiftUI provides the tools to create exactly what you need.