Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MKAnnotationView setImage() on iOS 11 has an animation

Since iOS 11, when I use setImage in my custom MKAnnotationView, the image is displayed with an animation.

The problem is when I select and deselect the MKAnnotationView and the image has a different size. It results in a weird animation.

No problem until iOS 11, can we stop this behaviour?

like image 299
Fonzie f Avatar asked Oct 23 '17 09:10

Fonzie f


1 Answers

Seeing that this is the only mention of this issue (feature?) that I've found anywhere over the past year and a half, let me first thank you for being my only source of sanity. With that, I've finally been able to circle back and demystify this...sort of. I've yet to file a radar, only because it is unclear whether this behavior is intended. Whatever the correct behavior, the animation has some odd side-effects, and led me to report a separate issue.

The Problem

To boil it down, the image field is not an animatable field, but updating it outside of a user-defined transaction triggers a system-performed path animation from the previous image to the new image. Since the animations are performed at the layer-level, UIKit will not be able to stop them. Instead, we have to halt the current thread transaction's animation actions entirely so that the nested layer animations are never allowed to perform:

CATransaction.begin()
CATransaction.setDisableActions(true)
annotationView.image = newImage
CATransaction.commit()

Modifying Existing Animation

Rather than cutting off the animation completely, you can also de-jank the path animation by injecting your own properties to the current thread transaction. The catch here, though, is that in order to correctly animate the path & make the appearance that the image isn't moving (ex. animating from a small pin to a large pin), we have to synchronize an additional animation of the centerOffset. This field does not directly modify the layer, but it does appear to be a UIKit-animatable field. This means that you need to combine a UIKit animation with a CoreAnimation transaction:

CATransaction.begin()
CATransaction.setAnimationDuration(animationDuration)
CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: .easeIn))

UIView.animate(withDuration: animationDuration, delay: 0, options: .curveEaseIn, animations: {
    self.image = newImage
    self.centerOffset = newCenterOffset
}, completion: nil)

CATransaction.commit()

The duration & timing function must be the same for both the UIKit animation & the CoreAnimation transaction.

like image 82
John Avatar answered Oct 21 '22 10:10

John