Matthew's Dev Blog

An indeterminate linear ProgressView that works on iOS

It's strange. This SwiftUI code, for a linear-style ProgressView, shows an indeterminate animation on macOS (Monterey 12.1) but not on iOS (15.2):

ProgressView()
	.progressViewStyle(LinearProgressViewStyle())

So let's make our own

struct IndeterminateProgressView: View {
	@State private var width: CGFloat = 0
	@State private var offset: CGFloat = 0
	@Environment(\.isEnabled) private var isEnabled

	var body: some View {
		Rectangle()
			.foregroundColor(.gray.opacity(0.15))
			.readWidth()
			.overlay(
				Rectangle()
					.foregroundColor(Color.accentColor)
					.frame(width: self.width * 0.26, height: 6)
					.clipShape(Capsule())
					.offset(x: -self.width * 0.6, y: 0)
					.offset(x: self.width * 1.2 * self.offset, y: 0)
					.animation(.default.repeatForever().speed(0.265), value: self.offset)
					.onAppear{
						withAnimation {
							self.offset = 1
						}
					}
			)
			.clipShape(Capsule())
			.opacity(self.isEnabled ? 1 : 0)
			.animation(.default, value: self.isEnabled)
			.frame(height: 6)
			.onPreferenceChange(WidthPreferenceKey.self) { width in
				self.width = width
			}
	}
}

You'll also need this read-width view modifier too, which reads the width of the view it's attached to. (I'd expect that most SwiftUI projects will already have this though!)

struct WidthPreferenceKey: PreferenceKey {
	static var defaultValue: CGFloat = 0

	static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
		value = max(value, nextValue())
	}
}

private struct ReadWidthModifier: ViewModifier {
	private var sizeView: some View {
		GeometryReader { geometry in
			Color.clear.preference(key: WidthPreferenceKey.self, value: geometry.size.width)
		}
	}

	func body(content: Content) -> some View {
		content.background(sizeView)
	}
}

extension View {
	func readWidth() -> some View {
		self
			.modifier(ReadWidthModifier())
	}
}

What does it look like?

macOS `LinearProgressViewStyle` {top} and the new `IndeterminateLinearProgressView` {bottom}

macOS LinearProgressViewStyle (top) and the new IndeterminateLinearProgressView (bottom). That's close enough for me.

Tagged with:

First published 6 January 2022