I'm desperately trying to morph a smallLabel
into a bigLabel
. By morphing, I mean transforming the following properties from one label to match the respective properties of the other label, with a smooth animation:
- font size
- font weight
- frame (i.e. bounds and position)
The desired effect should look similar to the animation that is applied to the navigation controller's title label when using large titles:
Now I'm aware of last year's WWDC session Advanced Animations with UIKit where they show how to do just that. However, this technique is pretty limited as it's basically just applying a transform to the label's frame and thus it only works if all properties other than the font size are identical.
The technique fails already when one label has a regular
font weight and the other a bold
weight – those properties do not change when applying a transform. Thus, I decided to dig a little deeper and use Core Animation for morphing.
First, I create a new text layer which I set up to be visually identical with the smallLabel
:
/// Creates a text layer with its text and properties copied from the label.
func createTextLayer(from label: UILabel) -> CATextLayer {
let textLayer = CATextLayer()
textLayer.frame = label.frame
textLayer.string = label.text
textLayer.opacity = 0.3
textLayer.fontSize = label.font.pointSize
textLayer.foregroundColor = UIColor.red.cgColor
textLayer.backgroundColor = UIColor.cyan.cgColor
view.layer.addSublayer(textLayer)
return textLayer
}
Then, I create the necessary animations and add them to this layer:
func animate(from smallLabel: UILabel, to bigLabel: UILabel) {
let textLayer = createTextLayer(from: smallLabel)
view.layer.addSublayer(textLayer)
let group = CAAnimationGroup()
group.duration = 4
group.repeatCount = .infinity
// Animate font size
let fontSizeAnimation = CABasicAnimation(keyPath: "fontSize")
fontSizeAnimation.toValue = bigLabel.font.pointSize
// Animate font (weight)
let fontAnimation = CABasicAnimation(keyPath: "font")
fontAnimation.toValue = CGFont(bigLabel.font.fontName as CFString)
// Animate bounds
let boundsAnimation = CABasicAnimation(keyPath: "bounds")
boundsAnimation.toValue = bigLabel.bounds
// Animate position
let positionAnimation = CABasicAnimation(keyPath: "position")
positionAnimation.toValue = bigLabel.layer.position
group.animations = [
fontSizeAnimation,
boundsAnimation,
positionAnimation,
fontAnimation
]
textLayer.add(group, forKey: "group")
}
Here is what I get:
As you can see, it doesn't quite work as intended. There are two issues with this animation:
The font weight doesn't animate but switches abruptly in the middle of the animation process.
While the frame of the (cyan colored) text layer moves and increases in size as expected, the text itself somehow moves towards the lower-left corner of the layer and is cut off from the right side.
My questions are:
1️⃣Why does this happen (especially 2.)?
and