Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Choppy CATextLayer animation: fontSize + position concurrently

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?

like image 649
slider Avatar asked Oct 12 '17 03:10

slider


1 Answers

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.

like image 108
slider Avatar answered Oct 04 '22 03:10

slider