Animate scaleEffect modifier between original to higher size

142 views Asked by At

I have an imageview in SwiftUI and i want to scale the view to higher size for fraction of second and return to it's normal size. How can i do this in SwiftUI?


Image("disagreeSmall")
.scaleEffect(scale)
.animation(.easeIn(duration: 0.15), value: selectedRating)
1

There are 1 answers

0
Benzy Neez On

If you are able to use functionality that was only introduced in iOS 17 then there are two easy ways to achieve this animation:

1. Use a .symbolEffect

The .bounce effect scales up and then down again:

Image("disagreeSmall")
    .symbolEffect(.bounce, value: selectedRating)

2. Add a completion callback to the animation

Use an animation to scale up, then when it completes, perform another animation to scale back down.

@State private var scale: CGFloat = 1

Image("disagreeSmall")
    .scaleEffect(scale)
    .onChange(of: selectedRating) { oldVal, newVal in
        withAnimation(.easeIn(duration: 0.15)) {
            scale = 1.1
        } completion: {
            withAnimation(.easeOut(duration: 0.15)) {
                scale = 1.0
            }
        }
    }

If you need to support older iOS versions then you could use other mechanisms to achieve a completion callback. This is how it could be done using the animation completion callback outlined in the answer to SwiftUI withAnimation completion callback:

// Credit to Centurion for the AnimatableModifier solution:
// https://stackoverflow.com/a/62855108/20386264
struct AnimationCompletionCallbackCGFloat: ViewModifier, Animatable {

    var targetValue: CGFloat
    var completion: () -> ()

    init(animatedValue: CGFloat, completion: @escaping () -> ()) {
        self.targetValue = animatedValue
        self.completion = completion
        self.animatableData = animatedValue
    }

    var animatableData: CGFloat {
        didSet {
            checkIfFinished()
        }
    }

    func checkIfFinished() -> () {
        if (animatableData == targetValue) {
            DispatchQueue.main.async {
                self.completion()
            }
        }
    }

    func body(content: Content) -> some View {
        content
    }
}
Image("disagreeSmall")
    .scaleEffect(scale)
    .onChange(of: selectedRating) { newVal in // pre iOS 17
        withAnimation(.easeIn(duration: 0.15)) {
            scale = 1.1
        }
    }
    .modifier(
        AnimationCompletionCallbackCGFloat(animatedValue: scale) {
            withAnimation(.easeOut(duration: 0.15)) {
                scale = 1.0
            }
        }
    )