Search code examples
iosanimationswiftuinavigationview

Animating a ProgressViewStyle inside NavigationView


Let's make a simple custom "infinite" spinner.

SwiftUI provides ProgressView as the prototypical container for this with ProgressViewStyle as a means of customizing the way to present the progress.

To make our spinner infinitely spin, we'll use a linear animation, repeat it forever, start it at a rotation effect of 0 and move it to 360 degrees, as soon as the spinner appears.

Simple!

Now let's put it inside a NavigationView 🔥:

spinner animates up while spinning around

We see the spinner bouncing up and down as it spins. Any clue what's going on or how to address the issue (while preserving a respect for the fundamentals, eg. ProgressView to display progress and ProgressViewStyle to customize it)?

struct SimplePreview: PreviewProvider, View {
    static var previews = Self()
    
    @State
    private var isAnimating = false

    var body: some View {
        NavigationView {
            ProgressView().progressViewStyle(SymbolicStyle())
        }
    }
    
    public struct SymbolicStyle: ProgressViewStyle {
        @State
        private var isAnimating = false

        public func makeBody(configuration: Configuration) -> some View {
            Image(systemName: "circle.hexagonpath.fill")
                .rotationEffect(self.isAnimating ? .degrees(360) : .zero)
                .onAppear {
                    withAnimation(.linear(duration: 1).repeatForever(autoreverses: false)) {
                        self.isAnimating = true
                    }
                }
        }
    }
}

Solution

  • You can replace .onAppear with .task in order to start the animation in a separate context where it does not get mixed up with other unreleated layout events.