Search code examples
iosswiftuiviewcgaffinetransform

How to animate scaling and moving a UILabel, then set its transform back to the identity when finished and preserve its frame?


I have a UILabel that I am attempting to scale and translate, and I want to animate this. I am doing this by setting its transform in a UIView.animate block. When the animation is finished, I would like to set the view's transform back to .identity and update its frame so that it remains exactly where the CGAffineTransform moved it. Pseudocode:

func animateMovement(label: UILabel,
                     newWidth: CGFloat,
                     newHeight: CGFloat,
                     newOriginX: CGFloat,
                     newOriginY: CGFloat)
{        
    UIView.animate(withDuration: duration, animations: {
        label.transform = ??? // Something that moves it to the new location and new size
    }) {
        label.frame = ??? // The final frame from the above animation
        label.transform = CGAffineTransform.identity
    }
}

As to why I don't simply assign the new frame in the animation block: I have text inside the label that I want to scale with the animation, which is not possible when animating changing the frame instead of the transform.

I'm having coordinate space problems, which I can tell because after the animation it is not in the correct location (the label has the wrong origin).


Solution

  • Here is the answer I came up with. This will scale the contents through the duration of the animation and then reset the object to have the identity transform when finished.

    static func changeFrame(label: UILabel,
                            toOriginX newOriginX: CGFloat,
                            toOriginY newOriginY: CGFloat,
                            toWidth newWidth: CGFloat,
                            toHeight newHeight: CGFloat,
                            duration: TimeInterval)
    {
        let oldFrame = label.frame
        let newFrame = CGRect(x: newOriginX, y: newOriginY, width: newWidth, height: newHeight)
    
        let translation = CGAffineTransform(translationX: newFrame.midX - oldFrame.midX,
                                            y: newFrame.midY - oldFrame.midY)
        let scaling = CGAffineTransform(scaleX: newFrame.width / oldFrame.width,
                                        y: newFrame.height / oldFrame.height)
    
        let transform = scaling.concatenating(translation)
    
        UIView.animate(withDuration: duration, animations: {
            label.transform = transform
        }) { _ in
            label.transform = .identity
            label.frame = newFrame
        }
    }