Friday, October 20, 2017

Choppy CATextLayer animation: fontSize + position concurrently

Leave a Comment

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?

1 Answers

Answers 1

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.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment