UILabel text change animation flickers when one text is longer than other

714 Views Asked by At

I am using the ideal solution to animate text changes to my UILabel. Similar to this answer https://stackoverflow.com/a/33705634/8704900
The problem is when the texts are not of same length, the animation is no more smooth and the text flickers before animating up.
Video: https://drive.google.com/file/d/1I89NnzjQp7TbemO-dmcbKzYUr7pM7mGk/view?usp=sharing

My code looks like this:

@IBOutlet weak var label: UILabel!

var titleLabelAnimation: CATransition = {
        let animation = CATransition()
        animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.default)
        animation.type = .push
        animation.subtype = .fromTop
        animation.duration = 0.5
        return animation
    }()

    @IBAction func didTap() {
        self.label.layer.add(self.titleLabelAnimation, forKey: nil)
        self.label.text = "Can you see a flicker?"
        self.label.sizeToFit()
    }
    
    @IBAction func didTapRev() {
        self.label.layer.add(self.titleLabelAnimation, forKey: nil)
        self.label.text = "Hello this is animation !!!"
        self.label.sizeToFit()
    }

I have tried layoutIfNeeded(), sizeToFit(), changing the text before animation and couple of other workarounds. Nothing seem to be working!

2

There are 2 best solutions below

1
Matic Oblak On BEST ANSWER

Not sure exactly what is going on in your example as I could not produce this result. But animations are in many cases a pain and a lot of things may produce jumping views.

By having a bit more control over animation you might have better luck finding out the issue or fixing it. For your specific case it might already be enough to do your animation using snapshots. Check out the following:

@IBOutlet weak var label: UILabel!

private func animateAsCustom(applyChanges: @escaping (() -> Void)) {
    guard let viewToMove = label else { return }
    guard let panel = viewToMove.superview else { return }
    guard let snapshotView = viewToMove.snapshotView(afterScreenUpdates: true) else { return }
    
    applyChanges()
    
    UIView.performWithoutAnimation {
        panel.addSubview(snapshotView)
        snapshotView.center = viewToMove.center
        viewToMove.transform = CGAffineTransform(translationX: 0.0, y: 50.0) // TODO: compute values for translation
        viewToMove.alpha = 0.0
    }
    UIView.animate(withDuration: 0.5, delay: 0.0, options: .curveEaseIn) {
        viewToMove.transform = .identity
        snapshotView.transform = CGAffineTransform(translationX: 0.0, y: -50.0)
        snapshotView.alpha = 0.0
        viewToMove.alpha = 1.0
    } completion: { _ in
        snapshotView.removeFromSuperview()
    }
}

@IBAction func didTap() {
    animateAsCustom {
        self.label.numberOfLines = 1
        self.label.text = "Can you see a flicker?"
        self.label.textColor = .red
        self.label.font = UIFont.systemFont(ofSize: 20)
    }
}

@IBAction func didTapRev() {
    animateAsCustom {
        self.label.numberOfLines = 0
        self.label.text = "Hello this\nis animation !!!"
        self.label.textColor = .black
        self.label.font = UIFont.systemFont(ofSize: 30)
    }
}

This will still not fix the issue if you press one of the buttons before previous animation did finish. To fix that one as well some extra effort may be needed. But for now this solution could be enough.

0
Andrey Komarov On

I had the same issue, but with a slightly different situation: I had two labels one under another. And the reason was that the labels were inside of the vertical UIStackView, which in turn was layouted in the center of the superview. I removed stack view and layouted labels just in the center of the superview, it worked for me.

So if your labels are inside of stack view, you can try to remove the stack view