Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
602 views
in Technique[技术] by (71.8m points)

ios - How to make zoom in zoom out button animation on tap gesture in SwiftUI?

Simple and regular approach to animate a bump effect for a button but not simple in SwiftUI.

I'm trying to change scale in tapGesture modifier, but it doesn't have any effect. I don't know how to make chain of animations, probably because SwiftUI doesn't have it. So my naive approach was:

@State private var scaleValue = CGFloat(1)

...

Button(action: {
    withAnimation {
        self.scaleValue = 1.5
    }

    withAnimation {
        self.scaleValue = 1.0
    }
}) {
    Image("button1")
        .scaleEffect(self.scaleValue)
}

Obviously it doesn't work and buttons image get last scale value immediately.

My second thought was to change scale to 0.8 value on hold event and then after release event make scale to 1.2 and again after few mseconds change it to 1.0. I guess this algorithm should make nice and more natural bump effect. But I couldn't find suitable gesture struct in SwiftUI to handle hold-n-release event.

P.S. For ease understanding, I will describe the steps of the hold-n-release algorithm:

  1. Scale value is 1.0
  2. User touch the button
  3. The button scale becomes 0.8
  4. User release the button
  5. The button scale becomes 1.2
  6. Delay 0.1 sec
  7. The button scale go back to default 1.0

UPD: I found a simple solution using animation delay modifier. But I'm not sure it's right and clear. Also it doens't cover hold-n-release issue:

@State private var scaleValue = CGFloat(1)

...

Button(action: {
    withAnimation {
        self.scaleValue = 1.5
    }

    //
    // Using delay for second animation block
    //
    withAnimation(Animation.linear.delay(0.2)) {
        self.scaleValue = 1.0
    }
}) {
    Image("button1")
        .scaleEffect(self.scaleValue)
}

UPD 2: I noticed in solution above it doesn't matter what value I pass as argument to delay modifier: 0.2 or 1000 will have same effect. Perhaps it's a bug ??

So I've used Timer instance instead of delay animation modifier. And now it's working as expected:

...

Button(action: {
    withAnimation {
        self.scaleValue = 1.5
    }

    //
    // Replace it
    //
    // withAnimation(Animation.linear.delay(0.2)) {
    //    self.scaleValue = 1.0
    // }
    //
    // by Timer with 0.5 msec delay
    //
    Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in
        withAnimation {
            self.scaleValue = 1.0
        }
    }
}) {
...

UPD 3: Until we waiting official Apple update, one of suitable solution for realization of two events touchStart and touchEnd is based on @average Joe answer:

import SwiftUI

struct TouchGestureViewModifier: ViewModifier {

    let minimumDistance: CGFloat
    let touchBegan: () -> Void
    let touchEnd: (Bool) -> Void

    @State private var hasBegun = false
    @State private var hasEnded = false

    init(minimumDistance: CGFloat, touchBegan: @escaping () -> Void, touchEnd: @escaping (Bool) -> Void) {
        self.minimumDistance = minimumDistance
        self.touchBegan = touchBegan
        self.touchEnd = touchEnd
    }

    private func isTooFar(_ translation: CGSize) -> Bool {
        let distance = sqrt(pow(translation.width, 2) + pow(translation.height, 2))
        return distance >= minimumDistance
    }

    func body(content: Content) -> some View {
        content.gesture(DragGesture(minimumDistance: 0)
            .onChanged { event in
                guard !self.hasEnded else { return }

                if self.hasBegun == false {
                    self.hasBegun = true
                    self.touchBegan()
                } else if self.isTooFar(event.translation) {
                    self.hasEnded = true
                    self.touchEnd(false)
                }
            }
            .onEnded { event in
                if !self.hasEnded {
                    let success = !self.isTooFar(event.translation)
                    self.touchEnd(success)
                }
                self.hasBegun = false
                self.hasEnded = false
            }
        )
    }
}

extension View {
    func onTouchGesture(minimumDistance: CGFloat = 20.0,
                        touchBegan: @escaping () -> Void,
                        touchEnd: @escaping (Bool) -> Void) -> some View {
        modifier(TouchGestureViewModifier(minimumDistance: minimumDistance, touchBegan: touchBegan, touchEnd: touchEnd))
    }
}

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)
struct ScaleButtonStyle: ButtonStyle {
    func makeBody(configuration: Self.Configuration) -> some View {
        configuration.label
            .scaleEffect(configuration.isPressed ? 2 : 1)
    }
}

struct Test2View: View {
    var body: some View {
        Button(action: {}) {
            Image("button1")
        }.buttonStyle(ScaleButtonStyle())
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...