Search code examples
iosswiftuiios-animations

SwiftUI: Animation Inside NavigationView


I am trying to create a simple animation in SwiftUI. It is basically a rectangle that changes its frame, while staying in the center of the parent view.

struct ContentView: View {
    var body: some View {
        NavigationView {
            VStack {
                Text("Text")
                ZStack {
                    Color.blue
                    SquareAnimation().frame(width: 200, height: 200, alignment: .center)
                }
                Text("Text")
            }
        }
    }
}

struct SquareAnimation: View {
    var currentRect = CGRect(x: 0, y: 0, width: 50, height: 50)
    var finalRect = CGRect(x: 0, y: 0, width: 100, height: 100)
    
    private let animation = Animation.easeInOut(duration: 1).repeatForever(autoreverses: true)
    
    @State var animate = false
    
    var body: some View {
        ZStack() {
            Color.clear
            Rectangle()
                .frame(width: animate ? finalRect.width: currentRect.width, height: animate ? finalRect.height: currentRect.height, alignment: .center)
                .animation(animation, value: animate)
                .onAppear() {
                    animate = true
                }
        }
        
    }
} 

The problem is, the black rectangle does not stay in the center if the NavigationView is used.
I have also used explicit animations with no avail. Why does the NavigationView affects the rectangle animation?


Solution

  • The onAppear is called too early when view frame is zero being in NavigationView, so animation is applied to change from zero to value.

    Here is valid workaround. Tested with Xcode 12.4 / iOS 14.4

    var body: some View {
        ZStack() {
            Color.clear
            Rectangle()
                .frame(width: animate ? finalRect.width: currentRect.width, height: animate ? finalRect.height: currentRect.height, alignment: .center)
                .animation(animation, value: animate)
                .onAppear {
                    DispatchQueue.main.async {   
                       // << postpone till end of views construction !!
                        animate = true
                    }
                }
        }
    }
    

    Note: almost any why question can be answered only by Apple... maybe it is a bug, maybe an implementation specifics.