Mathias Jepsen
Mathias Jepsen

Reputation: 134

Animating a UIView's alpha in sequence with UIViewPropertyAnimator

I have a UIView that I want to reveal after 0.5 seconds, and hide again after 0.5 seconds, creating a simple animation. My code is as follows:

    let animation = UIViewPropertyAnimator.init(duration: 0.5, curve: .linear) {
        self.timerBackground.alpha = 1
        let transition = UIViewPropertyAnimator.init(duration: 0.5, curve: .linear) {
            self.timerBackground.alpha = 0
        }
        transition.startAnimation(afterDelay: 0.5)
    }
    animation.startAnimation()

When I test it out, nothing happens. I assume it's because they're both running at the same time, which would mean they cancel each other out, but isn't that what the "afterDelay" part should prevent?

If I run them separately, i.e. either fading from hidden to visible, or visible to hidden, it works, but when I try to run them in a sequence, it doesn't work.

My UIView is not opaque or hidden.

Upvotes: 3

Views: 2610

Answers (3)

Marcin Żmigrodzki
Marcin Żmigrodzki

Reputation: 393

What worked for me, was using sequence of UIViewPropertyAnimators. Here is example of my code:

let animator1 = UIViewPropertyAnimator(duration:1, curve: .easeIn)
        animator1.addAnimations {
            smallCoin.transform = CGAffineTransform(scaleX: 4, y: 4)
            smallCoin.center = center
        }
        let animator2 = UIViewPropertyAnimator(duration:1, curve: .easeIn)
        animator2.addAnimations {
            center.y -= 20
            smallCoin.center = center
        }
        let animator3 = UIViewPropertyAnimator(duration:10, curve: .easeIn)
        animator3.addAnimations {
            smallCoin.alpha = 0
        }
        animator1.addCompletion { _ in
            animator2.startAnimation()
          }
        animator2.addCompletion { _ in
            animator3.startAnimation()
          }
        animator3.addCompletion ({ _ in
            print("finished")
        })
        animator1.startAnimation()

You can even add afterdelay attribute to manage speed of animations.

animator3.startAnimation(afterDelay: 10)

Upvotes: 0

Olga Konoreva
Olga Konoreva

Reputation: 1438

You can use Timer, and add appearing / hiding animations blocks on every timer tick to your UIViewPropertyAnimatorobject.

Here's a codebase:

@IBOutlet weak var timerBackground: UIImageView!

private var timer: Timer?
private var isShown = false
private var viewAnimator = UIViewPropertyAnimator.init(duration: 0.5, curve: .linear)

override func viewDidLoad() {
    super.viewDidLoad()
    viewAnimator.addAnimations {
        self.timerBackground.alpha = 1
    }
    viewAnimator.startAnimation()
    isShown = true

    self.timer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(self.startReversedAction), userInfo: nil, repeats: true)
}

func startReversedAction() {
    // stop the previous animations block if it did not have time to finish its movement
    viewAnimator.stopAnimation(true)
    viewAnimator.addAnimations ({
        self.timerBackground.alpha = self.isShown ? 0 : 1
    })
    viewAnimator.startAnimation()
    isShown  =  !isShown
}

I've implemented the very similar behavior for dots jumping of iOS 10 Animations demo project.

Please, feel free to look at it to get more details.

Upvotes: 7

Oleh Zayats
Oleh Zayats

Reputation: 2443

Use UIView.animateKeyframes you'll structure your code nicely if you have complicated animations. If you'll use UIView animations nested within the completion blocks of others, it will probably result in ridiculous indentation levels and zero readability.

Here's an example:

/* Target frames to move our object to (and animate)
   or it could be alpha property in your case... */

let newFrameOne = CGRect(x: 200, y: 50, width: button.bounds.size.width, height: button.bounds.size.height)
let newFrameTwo = CGRect(x: 300, y: 200, width: button.bounds.size.width, height: button.bounds.size.height)

UIView.animateKeyframes(withDuration: 2.0,
                               delay: 0.0,
                             options: .repeat,
                          animations: { _ in
    /* First animation */                                    
    UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.5, animations: { [weak self] in
        self?.button.frame = newFrameOne
    })

    /* Second animation */                                    
    UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5, animations: { [weak self] in
        self?.button.frame = newFrameTwo
    })

    /* . . . */  

    }, completion: nil)

Upvotes: 1

Related Questions