Search code examples
swiftuiswiftui-animationswiftui-button

How to animate a swiftUI button to display another view


I have a quite simple case, a view displays another one when a button is pressed.

The label on the second view is at the top of the view, which is working fine, unless I try to animate the button when it's pressed. In that case the label on the second view is sometimes at the top, sometimes lower, very eratic.

Here is the running code (simplified) :

import SwiftUI

struct ContentView2: View {
    @State private var displayScreen = ""
    
    var body: some View {
        ZStack {
            VStack(spacing: 20) {
                Group {
                    Button("  Play  ") {
                        displayScreen = "Level"
                    }
                    .padding(.top, 50)
                    .buttonStyle(Button2())
                }
                
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
            
            if displayScreen == "Level" {
                Level(displayedScreen: $displayScreen)
            }
        }
        .background(Color.yellow)
    }
}

struct Button2: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .padding(6)
            .background(Color.blue)
            .scaleEffect(configuration.isPressed ? 0.7 : 1)
            .animation(.interpolatingSpring(stiffness: 400, damping: 5, initialVelocity: 10), value: configuration.isPressed) // PROBLEM WITH THIS LINE !
            .foregroundColor(.white)
            
    }
}

struct Level: View {
    @Binding var displayedScreen: String

    var body: some View {
        NavigationView {
            ZStack(alignment: .top) {
                VStack {
                    Button("Back") {
                        displayedScreen = ""
                    }
                    .padding(.top)
                    Spacer()
                }
                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
            }
            .background(Color.green)
        }
    }
}

struct ContentView2_Previews: PreviewProvider {
    static var previews: some View {
        ContentView2()
    }
}

If I remove the .animation line, the behavior is consistent.

Is there a way to animate the button when pressed AND display the second view correctly ?

Thanks for advice :)

The problem


Solution

  • I ran your code and understood the problem. What I found is that the issue occurs only when the button is tapped and released before the animation ends (regardless of the type of animation used). Try holding the button for a few seconds and releasing it only after the animation is completely finished. It should work.

    However, it seems that the problem is related to the NavigationView used in the 'Level' view, not the animation itself.

    struct Level: View {
        @Binding var displayedScreen: String
    
        var body: some View {
    //        NavigationView { // -> this NavigationView is causing the problem
                ZStack(alignment: .top) {
                    VStack {
                        Button("Back") {
                            displayedScreen = ""
                        }
                        .padding(.top)
                        Spacer()
                    }
                    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
                }
                .background(Color.green)
    //        }
        }
    }
    

    I'm not sure about the purpose of using the NavigationView in the 'Level' view, but as far as I know, you should typically use a NavigationView in the root view. Perhaps consider restructuring the view hierarchy.