Search code examples
swiftbuttonswiftui

Perform button action after press button animation


I set a ButtonStyle to a button. On this button I set an animation with scale effect on pressing the button. Is there a way to perform the action only after the animation was completed or maybe with a delay? Now when I click the button the next view shows and you can't see the button animated.

struct CustomButtonStyle: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .foregroundColor(Color.blue)
            .padding(.vertical)
            .frame(height: 48)
            .frame(minWidth: 0, maxWidth: .infinity, alignment: .center)
            .background(Color.white)
            .cornerRadius(32)
            .scaleEffect(configuration.isPressed ? 0.9 : 1.0)
            .opacity(configuration.isPressed ? 0.6 : 1.0)
            .animation(.easeInOut, value: configuration.isPressed)
    }
}

struct TestView: View {
   var body: some View {
     Button(action: {
          // redirect to another view
     }, label: {
          Text("Test")
     }).buttonStyle(CustomButtonStyle())
   }
}

Solution

  • If you press the button for long enough, you will be able to see the scale effect (provided that your finger is smaller than the button label). And if you press the button only for a short enough moment, the scaling down would stop before it reaches 0.9, and wouldn't be very visible even if you waited for it to finish scaling it back up. IMO, what you have now is good enough.

    If you really want to add a delay, you would need to conform to PrimitiveButtonStyle, not ButtonStyle. PrimitiveButtonStyle.Configuration allows you to programmatically trigger() the button's action. However, it won't have isPressed, so you would have to implement that part on your own with a Gesture.

    struct CustomButtonStyle: PrimitiveButtonStyle {
        @State var pressed = false
        
        func makeBody(configuration: Configuration) -> some View {
            configuration.label
                .foregroundColor(Color.blue)
                .padding(.vertical)
                .frame(height: 48)
                .frame(minWidth: 0, maxWidth: .infinity, alignment: .center)
                .background(Color.white)
                .cornerRadius(32)
                .scaleEffect(pressed ? 0.9 : 1.0)
                .opacity(pressed ? 0.6 : 1.0)
                .animation(.easeInOut, value: pressed)
                .gesture(DragGesture(minimumDistance: 0).onChanged { _ in
                    pressed = true
                }.onEnded { value in
                    pressed = false
                    // optionally, use value.location and a geometry reader to determine whether
                    // the gesture ended inside the button's label
                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                        configuration.trigger()
                    }
                })
        }
    }