3

I'd like to animate my UIImageView with these steps:

  1. scale 2 times
  2. scaled image movement

So I'm trying like this:

UIView.animate(withDuration: 0.5, animations: {
   self.imageView.transform = CGAffineTransform(scaleX: 2, y: 2)                        
}) { _ in                        
   UIView.animate(withDuration: 1.0, animations: {
        self.imageView.transform = CGAffineTransform(translationX: -50, y: -50)
                        
   }) { _ in

        //other steps
   }
 }

But the result is that before moving imageView got back to previous size and then moving. How to move scaled view?

also I've tried to change layer's anchorPoint inside the animate block with no success

And maybe there is more simple way to achieve my goal: ability to zoom out image, then focus at 3 custom points, then zoom in to the initial size.

3

2 Answers 2

7

There are few approaches:

  1. One approach is to put the start of an animation in the completion handler of the prior animation:

    UIView.animate(withDuration: 1) { 
        …
    } completion: { _ in
        UIView.animate(withDuration: 2) { 
            …
        } completion: {  _ in 
            UIView.animate(withDuration: 1) { 
                …
            } completion: {  _ in 
                …
            }
        }
    }
    
  2. If you want to avoid a tower of nested animations (each in the completion handler of the prior one), you can use a keyframe animation, e.g. this is a five second animation with three separate keyframes, each with a duration of one third of the total, and whose start time is set accordingly:

    UIView.animateKeyframes(withDuration: 5, delay: 0) { 
        UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1 / 3) {
            …
        }
    
        UIView.addKeyframe(withRelativeStartTime: 1 / 3, relativeDuration: 1 / 3) {
            …
        }
    
        UIView.addKeyframe(withRelativeStartTime: 2 / 3, relativeDuration: 1 / 3) {
            …
        }
    }
    

    The above is for Swift 5.3 and later. For earlier versions, see previous revision of this answer.

  3. Nowadays, we might naturally reach for Swift concurrency (because it excels at managing dependencies between asynchronous tasks) and generally, Swift is pretty good at rendering async renditions of old Objective-C API with completion handler parameters (see SE-0297: Concurrency Interoperability with Objective-C: Asynchronous completion-handler methods). But the UIView animation functions are not (yet) elegantly mapped to async renditions. So we might just write our own async rendition of UIView animation API using a continuation:

    extension UIView {
        class func animate(
            for duration: Duration,
            delay: Duration = .zero,
            options: UIView.AnimationOptions = [],
            animations: @MainActor @Sendable @escaping () -> Void
        ) async {
            await withCheckedContinuation { continuation in
                UIView.animate(
                    withDuration: duration.timeInterval,
                    delay: delay.timeInterval,
                    options: options,
                    animations: animations
                ) { _ in
                    continuation.resume()
                }
            }
        }
    }
    
    extension Duration {
        var timeInterval: TimeInterval {
            let (wholeSeconds, attoseconds) = components
            return TimeInterval(wholeSeconds) + TimeInterval(attoseconds) / 1e18
        }
    }
    

    Then, from an asynchronous context, you can use Swift concurrency:

    await UIView.animate(for: .seconds(1), delay: .seconds(1.5)) {
        …
    }
    await UIView.animate(for: .seconds(1)) {
        …
    }
    await UIView.animate(for: .seconds(1)) {
        …
    }
    
Sign up to request clarification or add additional context in comments.

2 Comments

thanks for the answer. by the way, if there is any way to use .animateKeyFrames when I need to change imageView.image via UIView.transition block? or any other way? Because it looks pretty, but not for case with changing image (and time for changing every image is different, by the way)
That’s different from what was posed in the question. Am I inferring that you want to animate the changing of the image (e.g. with a cross dissolve or the like)? In that case, you’d use the first pattern, but rather than UIView.animate(withDuration:animations:completion:), you’d use UIView.transition(with:duration:options:animations:completion:).
1

For your first question: Since you are assigning the transform to a new one without keeping the old configuration, so in your second animation, your view will transform to the default scale (1, 1). All you need is just assigning the transform to a new one which contains all the old configurations plus the new you want.

UIView.animate(withDuration: 0.5, animations: {
  self.button.transform = self.button.transform.scaledBy(x: 2, y: 2)
}) { _ in
  UIView.animate(withDuration: 1.0, animations: {
    self.button.transform = self.button.transform.translatedBy(x: -50, y: -50)
  }) { _ in
    
  }
}

It's even better if you use animateKeyframes

5 Comments

Thank you! Solved my problem! And for your comments - I need show specific quarter of the image, like top left for 3 sec, then top right for 2 sec, then back to the center and scale to 1. So I guess that changing anchorPoint is the better way for this? animatedKeyFrames is a goos advice, thank you
For the focus, I think the easiest way is just to calculate movement vector and then translate the view so that the point you want to be focused on is displayed in your desired area. No need to change the layer's anchorPoint in this case.
thanks for the answer. by the way, if there is any way to use .animateKeyFrames when I need to change imageView.image via UIView.transition block? or any other way? Because it looks pretty, but not for case with changing image (and time for changing every image is different, by the way)
What do you mean imageView.image via UIView.transition block ?
at some steps of my animation (about 20 times) I need to change image with fade effect, so I'm using UIView.transition for this. and times to show each image is different

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.