iosswiftuiviewanimationuiviewpropertyanimator

How to chain animations using key frames


I want the arranged subviews of a stack view to become visible in a cascaded manner, but they're all appearing at the same time:

let i = 5
let animation = UIViewPropertyAnimator(duration: 5, timingParameters: UICubicTimingParameters())
animation.addAnimations {
    UIView.animateKeyframes(withDuration: 0, delay: 0, options: .calculationModeCubicPaced) {
        for index in 0..<i {
            UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: Double(1/i)) {
                let label = self.stackView.arrangedSubviews[index]
                label.alpha = 1
            }
        }
    } completion: { (_) in
        return
    }
}

animation.startAnimation()

My expectation is that setting the options to calculationModePaced or calculationModeCubicPaced animates each key frame in an evenly spaced-out manner even if I don't set the withRelativeStartTime parameter individually (and have them at 0).

I've tried changing the option to calculationModeLinear and manually setting the withRelativeStartTime:

UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) {
    let label = self.stackView.arrangedSubviews[0]
    label.alpha = 1
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5) {
    let label = self.stackView.arrangedSubviews[1]
    label.alpha = 1
}

but, the labels still show up at the same time.


Solution

  • To make it work as you expected you should:

    1. remove , options: .calculationModeCubicPaced
    2. fix relativeDuration - 1.0 / Double(i)
    3. setup withRelativeStartTime - Double(index) / Double(i)

    example:

    let totalCount = labels.count
    let labelDuration = 1.0 / Double(totalCount)
    
    let animation = UIViewPropertyAnimator(duration: 5, timingParameters: UICubicTimingParameters())
    animation.addAnimations {
        UIView.animateKeyframes(withDuration: 0, delay: 0, animations: { [weak self] in
            for i in 0..<totalCount {
                UIView.addKeyframe(withRelativeStartTime: Double(i) / Double(totalCount), relativeDuration: labelDuration) {
                    self?.labels[i].alpha = 1
                }
            }
        })
    }
    
    animation.startAnimation()