DanielZanchi
DanielZanchi

Reputation: 2768

Why does an animation in a NavigationView starts from the top left corner?

I'm trying to create a simple animation in SwiftUI and I would like the animation to start from center top and end to the center of the screen.
But, as you can see in the video, the animation starts from the top left corner when I use a NavigationView:
enter image description here

This is the code I am using for this example:

struct ContentView: View {
    
    @State var show = false
    
    var body: some View {
        NavigationView {
            HStack {
                Text("Hello, world!")
                    .padding()
                    .background(Color.blue)
                    .offset(x: 0, y: show ? 0 : -30)
                    .animation(Animation.easeOut.delay(0.6))
                    .onAppear {
                            self.show = true
                    }
                    .navigationTitle("Why?")
            }
        }
    }
}

Upvotes: 13

Views: 3006

Answers (4)

Asperi
Asperi

Reputation: 258097

Why does an animation in a NavigationView starts from the top left corner?

Because implicit animation affects all animatable properties, including position, which on creation time is CGPoint.zero (ie. top-left corner).

Such cases are solved by linking animation to dependent state value, thus it activates only when state changed, so all animatable properties not affected.

Here is possible variant for your case. Tested with Xcode 12.1 / iOS 14.1.

struct ContentView: View {
    
    @State var show = false
    
    var body: some View {
        GeometryReader { gp in
            NavigationView {
                    HStack {
                         Text("Hello, world!")
                              .padding()
                              .background(Color.blue)
                              .offset(x: 0, y: show ? 0 : -gp.size.height / 2)
                              .animation(Animation.easeOut.delay(0.6), value: show)
                              .onAppear {
                                         self.show = true
                              }
                              .navigationTitle("Why?")
                    }
            }
        }
    }
}

Upvotes: 6

Eva Madrazo
Eva Madrazo

Reputation: 4731

An easier solution in 5.6 for this is to use withAnimation:

struct ContentView: View {
    @State var offset = 0.0
    
    var body: some View {
        NavigationView {
            HStack {
                Text("Hello, world!")
                    .padding()
                    .background(Color.blue)
                    .offset(x: 0, y: offset)
                    .onAppear {

                        withAnimation(Animation.easeOut) { offset -= 120 }
                    }
                    .navigationTitle("Why?")
            }
        }
    }
}

Upvotes: 1

lopes710
lopes710

Reputation: 415

You can wrap self.show = true with DispatchQueue.main.async. Like this:

.onAppear {
    DispatchQueue.main.async {
        self.show = true
    }
}

Upvotes: 4

DanielZanchi
DanielZanchi

Reputation: 2768

So the solution is to use the value parameter on the animation and it works:

struct ContentView: View {
    
    @State var show = false
    
    var body: some View {
        NavigationView {
            HStack {
                Text("Hello, world!")
                    .padding()
                    .background(Color.blue)
                    .offset(x: 0, y: show ? 0 : -120)
                    .animation(Animation.easeOut.delay(0.6), value: show)
                    .onAppear {
                            self.show = true
                    }
                    .navigationTitle("Why?")
            }
        }
    }
}

Upvotes: 2

Related Questions