Reading time: 1 min

This recipe shows how to implement an indefinite loading bar in SwiftUI. This kind of view is common in Android Material Design as it's sleek and takes up less space than a conventional, circular LoadingView. The end result looks like this:

The code for this is quite simple:

  1. Render a Rectangle that fits its parent's width.
  2. Partly cover it by another Rectangle in its overlay. Use a GeometryReader to determine the overlay width.
  3. Animate the overlay's translation by changing its offset:
    • minOffset puts the top rectangle fully to the left of the bottom rectangle, effectively hiding it from sight.
    • maxOffset puts the top rectangle fully to the right, again hiding it from sight.
  4. Make sure that the Animation uses autoreverses: true in repeatForever in order to reset the top rectangle to minOffset at each iteration.

Here's the code:

// the height of the bar
private let height: CGFloat = 4
// how much does the blue part cover the gray part (40%)
private let coverPercentage: CGFloat = 0.4
private let minOffset: CGFloat = -2
private let maxOffset = 1 / coverPercentage * abs(minOffset)

struct InfiniteProgressBar: View {
  @State private var offset = minOffset;

  var body: some View {
    Rectangle()
      .foregroundColor(.gray) // change the color as you see fit
      .frame(height: height)
      .overlay(GeometryReader { geo in
        overlayRect(in: geo.frame(in: .local))
      })
  }

  private func overlayRect(in rect: CGRect) -> some View {
    let width = rect.width * coverPercentage
    return Rectangle()
      .foregroundColor(.blue)
      .frame(width: width)
      .offset(x: width * offset)
      .onAppear {
        withAnimation(Animation.linear(duration: 2.5).repeatForever(autoreverses: false)) {
          self.offset = maxOffset;
        }
      }
  }
}

Next Post Previous Post