1
votes

I have whittled this issue down as part of a larger project that has a lot of complex animations that were all working well until I wrapped the view in a NavigationView. After that all of the views decided they wanted to trigger all sorts of positional animation, and I had views all sliding into place when the view showed up. A lot of these animations were explicit and tied to events, so there should have been no animation at all. I went removed all the implicit animations and the problem persisted. It seems that is a view has an animation attached to it, then the NavigationView will positionally animate it onAppear

Here is a minimum example that reproduces it:

struct ContentView: View {
    @State var opacity = 0.5

    var body: some View {
        NavigationView {
            Circle()
                .opacity(opacity)
                .animation(.default)
        }
    }
}

In this example there is not a single animation trigger yet the NavigationView still animates it in. Removing the NavigationView will make it act as normal.

I am assuming it is a bug in the NavigationView implementation where they probably using slide transitions to segue into the linked views, which is applying it to its sub views.

Does anyone know or can figure out a workaround for this?

Similar but questions with unsuitable answers are: What might be causing this animation bug with SwiftUI and NavigationView?

SwiftUI: Broken explicit animations in NavigationView?

Current as of iOS 14.2 Xcode 12.2

Edit: It appears the issues lies with having more than one view under a ZStack or overlay. In the following code, if you comment out the Color.black view you will see the animation digresses into onAppear transitions for the animated view (circle)

import SwiftUI

struct ProgressCircle: View {
    @State var show = false
    let progress: Double
    
    let foreColor: Color
    let backgroundColor: Color
    let strokeWidth: Double
    
    init(foregroundColor: Color, backgroundColor: Color, strokeWidth: Double, progress: Double) {
        self.foreColor = foregroundColor
        self.backgroundColor = backgroundColor
        self.strokeWidth = strokeWidth
        self.progress = progress
    }
    
    var body: some View {
            ZStack {
                Circle()
                    .stroke(backgroundColor, style:
                                StrokeStyle(lineWidth: CGFloat(strokeWidth), lineCap: .round, lineJoin: .round))

                Circle()
                    .trim(from: 0 , to: self.show ? CGFloat(max(self.progress, 0.01)) : 0.01)
                    .stroke(foreColor,
                            style: StrokeStyle(lineWidth: CGFloat(strokeWidth), lineCap: .round, lineJoin: .round))
                    .rotationEffect(.degrees(-90))
                    .animation(.easeInOut(duration: 2), value: self.show)
                    .animation(.easeInOut(duration: 2), value: self.progress)
                    .onAppear {
                        self.show = true
                    }
            }
    }
}

struct ProgressCircle_Previews: PreviewProvider {
    static var previews: some View {
        NavigationView {
            ScrollView {
                ZStack {
//                    Color.black
                    
                    ProgressCircle(
                        foregroundColor: Color.black,
                        backgroundColor: Color.red,
                        strokeWidth: 15,
                        progress: 0.7)
                        .frame(width: 200, height: 200)
                        .background(Color.blue)
                }
            }
            .navigationTitle("Hello")
        }
    }
}
1

1 Answers

3
votes

Generic animation applied to all animatable properties. Make animation explicitly per-state, so it will be activated only for property dependent on that state changed:

struct ContentView: View {
    @State var opacity = 0.5

    var body: some View {
        NavigationView {
            Circle()
                .opacity(opacity)
                .animation(.default, value: opacity)   // << here !!
        }
    }
}