'Animate a UILabel and UIButton within a single UIView.transition block?

I need to animate the change of the value of a UILabel and a UIButton's image property.

I currently use two UIView.transition blocks which seem to work fine, but it seems awful and Im sure theres a far better way Im missing to avoid having duplicated code. Currently doing this:

UIView.transition(with: label, duration: 1, options: [.curveEaseInOut, .transitionCrossDissolve], animations: {
        self.label.text = someText
}, completion: nil)

UIView.transition(with: aButton, duration: 1, options: [.curveEaseInOut, .transitionCrossDissolve], animations: {
        self.aButton.setImage(aUIImage, for: .normal)
}, completion: nil)

Afaik theres no init for UIView.transition which takes in an array of UIViews to animate or something similar.

Thanks!



Solution 1:[1]

Concept

Looks like there are no 'built-in' ways to transition multiple views within one UIView.transition animation block.

Instead, we can try to make our own if we had a way to control transitions without blocks. Enter CATransition!

You can use CATransition to add cross-dissolve effects to CALayer like so:

let transition = CATransition()
transition.duration = 0.3
transition.type = .fade
view.layer.add(transition, forKey: "transition")

Answer

CATransition objects can be added to multiple layers at the same time. So we can use this knowledge to write our own code that applies this transition to multiple layers (or views) at the same time.

I went ahead and made a UIView extension for you. The stripped down version looks like this:

extension UIView{

    class func groupTransition(
        with views: [UIView],
        duration: TimeInterval,
        animations: (() -> Void)?
    ){
        let transition = CATransition()
        transition.duration = duration
        transition.type = .fade
        
        for v in views{
            v.layer.add(transition, forKey: "transition")
        }
        
        animations?()
    }
}

You can pass the list of views you want to transition and perform all the transition changes in a single block.

UIView.groupTransition(
    with: [label, aButton],
    duration: 0.3
) {
    self.label.text = self.getRandomText()
    self.aButton.setImage(self.getRandomImage(), for: .normal)
}

Discussion

I added some more functionality (like animation curve, delay, etc.) and made a more usable version of this and posted it here.

Of course there are disadvantages with this approach. You lose all the fancy transitions built in such as the "flip" transition. This is because the CoreAnimation Transition API only has some basic (but nice) effects baked in.

Feel free to fork the repo and tweak it for your liking.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1