I have a very strange issue in my app. I'm using UIViewPropertyAnimator
to animate changing images inside UIImageView
.
Sounds like trivial task but for some reaso my view ends up changing the image instantly so I end up with images flashing at light speed instead of given duration and delay parameters.
Here's the code:
private lazy var images: [UIImage?] = [
UIImage(named: "widgethint"),
UIImage(named: "shortcuthint"),
UIImage(named: "spotlighthint")
]
private var imageIndex = 0
private var animator: UIViewPropertyAnimator?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
animator = repeatingAnimator()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
animator?.stopAnimation(true)
}
private func repeatingAnimator() -> UIViewPropertyAnimator {
return .runningPropertyAnimator(withDuration: 2, delay: 6, options: [], animations: {
self.imageView.image = self.images[self.imageIndex]
}, completion: { pos in
if self.imageIndex >= self.images.count - 1 {
self.imageIndex = 0
} else {
self.imageIndex += 1
}
self.animator = self.repeatingAnimator()
})
}
So, according to the code animation should take 2 seconds to complete and start after 6 seconds delay, but it starts immediately and takes milliseconds to complete so I end up with horrible slideshow. Why could it be happening?
I also tried using the UIViewPropertyAnimator(duration: 2, curve: .linear)
and calling repeatingAnimator().startAnimation(afterDelay: 6)
but the result is the same.
Okay, I've figured it out, but it's still a bit annoying that such simple task cannot be done using the "Modern" animation API.
So, animating images inside UIImageView
is apparently not supported by UIViewPropertyAnimator
, during debugging I tried animating view's background color and it was working as expected. So I had to use Timer
instead and old UIView.transition(with:)
method
Here's the working code:
private let animationDelay: TimeInterval = 2.4
private var animationTimer: Timer!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
setAnimation(true)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
setAnimation(false)
}
private func setAnimation(_ enabled: Bool) {
guard enabled else {
animationTimer?.invalidate()
animationTimer = nil
return
}
animationTimer = Timer.scheduledTimer(withTimeInterval: animationDelay, repeats: true) { _ in
UIView.transition(with: self.imageView, duration: 0.5, options: [.curveLinear, .transitionCrossDissolve], animations: {
self.imageView.image = self.images[self.imageIndex]
}, completion: { succ in
guard succ else {
return
}
if self.imageIndex >= self.images.count - 1 {
self.imageIndex = 0
} else {
self.imageIndex += 1
}
})
}
}
Hopefully it will save someone some headaches in the future