Ilja
Ilja

Reputation: 46479

How to create custom button and animation for pressed and released state in Swift UI?

I am working on a view that will act as a custom button. It consists of 2 elements a ZStack and Image that should animate when button is pressed in and released. Both have slightly different animation. To do this I am essentially toggling a state upon which animations are based.

I was able to get where I want with a following onLongPressGesture property, but it has issues, mainly:

  1. I can't seem to be able to add onTapGesture to this button to perform an action. And doing so inside long pres's perform: {} requires me to wait for a second or so without releasing a button, which is not ideal. Ideally action should be performed once user releases a finger.
  2. I want button to stay in "pressed" animation state while user is holding their finger on it, but it looks like that after 1s or so it returns to it's default state.

I'm not sure long press is the right tool to use here, but I can't seem to find any other way that allows me to explicitly toggle my state with granular control. Ideally I would know exactly when button is pressed and when it is released, with action executing inside .onTapGesture

struct ButtonIcon: View {
  // Public Variables
  let resource: ImageResource
  
  // Private Variables
  @State private var taping = false
  
  // Body
  var body: some View {
    ZStack {
      Image(resource)
        .resizable()
        .scaledToFit()
        .frame(width: 24, height: 24)
        .scaleEffect(taping ? 1.1 : 1)
    }
    .padding(12)
    .scaleEffect(taping ? 0.8 : 1)
    .onLongPressGesture(
      perform: {},
      onPressingChanged: { pressing in
        withAnimation(.smooth(duration: 0.2)) {
          taping.toggle()
        }
      }
    )
  }
}

Upvotes: 1

Views: 1971

Answers (1)

Asad
Asad

Reputation: 331

You may use Drag gesture.

struct CustomButtom: View {
    @State private var tapping: Bool = false

    var body: some View {
        ZStack {
            Image(systemName: "heart.fill")
                .resizable()
                .scaledToFit()
                .foregroundStyle(Color.pink)
                .frame(width: 100, height: 100)
                .scaleEffect(tapping ? 1.1 : 1)
        }
        .padding(12)
        .scaleEffect(tapping ? 0.8 : 1)
        .gesture(
            DragGesture(minimumDistance: 0)
                .onChanged({ value in
                    withAnimation(.smooth(duration: 0.2)) {
                        tapping = true
                    }
                })
                .onEnded({ value in
                    withAnimation(.bouncy(duration: 0.5)) {
                        tapping = false
                    }
                })
        )

        Text(tapping ? "Pressing" : "Not pressing")
    }
}

Output: https://i.sstatic.net/FJ5zO.gif

Upvotes: 3

Related Questions