I need to animate a CATextLayer
's bounds.size.height
, position
, and fontSize
. When I add them to a CAAnimationGroup
, the text jitters during the animation, just like this:
https://youtu.be/HfC1ZX-pbyM
The jittering of the text's tracking values (spacing between characters) seems to occur while animating fontSize
with bounds.size.height
AND/OR position
. I've isolated fontSize
, and it performs well on its own.
How can I prevent the text from jittering in CATextLayer
if I animate bounds and font size at the same time?
EDIT
I've moved on from animating bounds
. Now, I only care about fontSize
+ position
. Here are two videos showing the difference.
fontSize
only (smooth): https://youtu.be/FDPPGF_FzLI
fontSize
+ position
(jittery): https://youtu.be/3rFTsp7wBzk
Here is the code for that.
let startFontSize: CGFloat = 16
let endFontSize: CGFloat = 30
let startPosition: CGPoint = CGPoint(x: 40, y: 100)
let endPosition: CGPoint = CGPoint(x: 20, y: 175)
// Initialize the layer
textLayer = CATextLayer()
textLayer.string = "Hello how are you?"
textLayer.font = UIFont.systemFont(ofSize: startFontSize, weight: UIFont.Weight.semibold)
textLayer.fontSize = startFontSize
textLayer.alignmentMode = kCAAlignmentLeft
textLayer.foregroundColor = UIColor.black.cgColor
textLayer.contentsScale = UIScreen.main.scale
textLayer.isWrapped = true
textLayer.backgroundColor = UIColor.lightGray.cgColor
textLayer.anchorPoint = CGPoint(x: 0, y: 0)
textLayer.position = startPosition
textLayer.bounds.size = CGSize(width: 450, height: 50)
view.layer.addSublayer(textLayer)
// Animate
let damping: CGFloat = 20
let mass: CGFloat = 1.2
var animations = [CASpringAnimation]()
let fontSizeAnim = CASpringAnimation(keyPath: "fontSize")
fontSizeAnim.fromValue = startFontSize
fontSizeAnim.toValue = endFontSize
fontSizeAnim.damping = damping
fontSizeAnim.mass = mass
fontSizeAnim.duration = fontSizeAnim.settlingDuration
animations.append(fontSizeAnim)
let positionAnim = CASpringAnimation(keyPath: "position.y")
positionAnim.fromValue = textLayer.position.y
positionAnim.toValue = endPosition.y
positionAnim.damping = damping
positionAnim.mass = mass
positionAnim.duration = positionAnim.settlingDuration
animations.append(positionAnim)
let animGroup = CAAnimationGroup()
animGroup.animations = animations
animGroup.duration = fontSizeAnim.settlingDuration
animGroup.isRemovedOnCompletion = true
animGroup.autoreverses = true
textLayer.add(animGroup, forKey: nil)
My device is running iOS 11.0.
EDIT 2
I've broken down each animation (fontSize
only, and fontSize
+ position
) frame-by-frame. In each video, I'm progressing 1 frame at a time.
In the fontSize
only video (https://youtu.be/DZw2pMjDcl8), each frame yields an increase in fontSize
, so there's no choppiness.
In the fontSize
+ position
video (https://youtu.be/_idWte92F38), position
is updated in every frame, but not fontSize
. There is only an increase in fontSize
in 60% of frames, meaning that fontSize
isn't animating in sync with position
, causing the perceived chopping.
So maybe the right question is: why does fontSize
animate in each frame when it's the only animation added to a layer, but not when added as part of CAAnimationGroup
in conjunction with the position
animation?
Apple DTS believes this issue is a bug. A report has been filed.
In the meantime, I'll be using CADisplayLink
to synchronize the redrawing of CATextLayer.fontSize
to the refresh rate of the device, which will redraw the layer with the appropriate fontSize
in each frame.
Edit
After tinkering with CADisplayLink
for a day or so, drawing to the correct fontSize
proved difficult, especially when paired with a custom timing function. So, I'm giving up on CATextLayer
altogether and going back to UILabel
.
In WWDC '17's Advanced Animations with UIKit, Apple recommends "view morphing" to animate between two label states — that is, the translation, scaling, and opacity blending of two views. UIViewPropertyAnimator
provides a lot of flexibility for this, like blending multiple timing functions and scrubbing. View morphing is also useful for transitioning between 2 text
values without having to fade out the text representation, changing text
, and fading back in.
I do hope Apple can beef up CATextLayer
support for non-interactive animations, as I'd prefer using one view to animate the same text representation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With