Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI: Broken explicit animations in NavigationView?

When I put an explicit animation inside a NavigationView, as an undesirable side effect, it animates the initial layout of the NavigationView content. It gets especially bad with infinite animations. Is there a way to disable this side effect?

Example: the image below is supposed to be an animated red loader on a full screen blue background. Instead I get this infinite loop of a scaling blue background:

enter image description here

import SwiftUI

struct EscapingAnimationTest: View {
    var body: some View {
        NavigationView {
            VStack {
                Spacer()
                EscapingAnimationTest_Inner()
                Spacer()
            }
            .backgroundFill(Color.blue)
        }
    }
}

struct EscapingAnimationTest_Inner: View {
    @State var degrees: CGFloat = 0
    
    var body: some View {
        Circle()
            .trim(from: 0.0, to: 0.3)
            .stroke(Color.red, lineWidth: 5)
            .rotationEffect(Angle(degrees: degrees))
            .onAppear() {
                withAnimation(Animation.linear(duration: 1).repeatForever(autoreverses: false)) {
                    degrees = 360
                }
            }
    }
}

struct EscapingAnimationTest_Previews: PreviewProvider {
    static var previews: some View {
        EscapingAnimationTest()
    }
}
like image 904
Arman Avatar asked Oct 28 '20 04:10

Arman


1 Answers

Here is fixed part (another my answer with explanations is here).

Tested with Xcode 12 / iOS 14.

demo

struct EscapingAnimationTest_Inner: View {
    @State var degrees: CGFloat = 0
    
    var body: some View {
        Circle()
            .trim(from: 0.0, to: 0.3)
            .stroke(Color.red, lineWidth: 5)
            .rotationEffect(Angle(degrees: Double(degrees)))
            .animation(Animation.linear(duration: 1).repeatForever(autoreverses: false), value: degrees)
            .onAppear() {
                DispatchQueue.main.async {   // << here !!
                    degrees = 360
                }
            }
    }
}

Update: the same will be using withAnimation

.onAppear() {
    DispatchQueue.main.async {
        withAnimation(Animation.linear(duration: 1).repeatForever(autoreverses: false)) {
           degrees = 360
        }
    }

}
like image 95
Asperi Avatar answered Oct 31 '22 21:10

Asperi