UIKit animate text alpha

I have a very simple sample I created to test animating text using UIKIt...


I created 3 subviews and put them on the main view

  • each subview has a UILabel in it
  • the labels all have an alpha of 0 specified in their properties
  • outlets for the labels are in my controller
  • controller code:



override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
       
        let duration = TimeInterval(5)
       
        UIView.animate(withDuration: duration, delay: 0.0, animations: {
            self.testText.alpha = 1.0
        })
       
        UIView.animate(withDuration: duration, delay: duration, animations: {
            self.testText2.alpha = 1.0
        })
       
        UIView.animate(withDuration: duration, delay: duration * 2, animations: {
            self.testText3.alpha = 1.0
        })
       
        UIView.animate(withDuration: 3, delay: duration * 3, animations: {
            self.testText.alpha = 0.0
            self.testText2.alpha = 0.0
            self.testText3.alpha = 0.0
        })
       
       
       
    }


Without the last animate method call, the 3 labels fade in, in sequence.

I want to fade them all out at the same time, after the last one has completely faded in.


I had expected my last animate method call to do that but, instead, the result is all of labels are displayed as alpha 1 at the beginning of the scene and then after 15 second, they all fade out.

Why does adding the last animate method call negate the effects of the first 3 animate method calls?

What should I be doing to achieve the effect I want?

Accepted Reply

I may agree with you that documentation should be imporved (not only this one). Thanks to give feedback on the answer you get.


However, what I try to explain is that concatenation does not take into account the delay you set. It just concatenate 2 animations on the same object. You may regret it, but that's it. Your code may be logical but not correct. That's why it is advised everywhere to use completion.


BTW, there is a similar situation when you deal with transform, if you've ever tried.

A bit confusing at the beginning.

Replies

Do you want first to fade in (set alpha to 1.0).


And once completed, fade out (alpha = 0) ?


The problem you have is that you "stack" several animations on the same objects ; and the animation engine does its own optimisations.


I changed by putting the fade out in completion of 3rd animation (Note: I renamed the texts as label1, …), and it works as expected.


    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
       
        let duration = TimeInterval(2)
        self.label1.alpha = 0.0     // To start from alpha 0.0
        self.label2.alpha = 0.0
        self.label3.alpha = 0.0

        UIView.animate(withDuration: duration, delay: 0.0, animations: {
            self.label1.alpha = 1.0
        })
       
        UIView.animate(withDuration: duration, delay: duration, animations: {
            self.label2.alpha = 1.0
        })
       
        UIView.animate(withDuration: duration, delay: duration * 2, animations: {
            self.label3.alpha = 1.0
        }, completion: { (complete: Bool) in
            UIView.animate(withDuration: 3, delay: duration * 3, animations: {
                self.label1.alpha = 0.0
                self.label2.alpha = 0.0
                self.label3.alpha = 0.0
            })

        })
        /*
        UIView.animate(withDuration: 3, delay: duration * 3, animations: {
            self.label1.alpha = 0.0
            self.label2.alpha = 0.0
            self.label3.alpha = 0.0
        })
        */
       
    }

Yes to both of your question...fade in and fade out.


Thanks for the suggestion and sample code fix. The completion on the last animation blocks works. I also found that using keyframes is another method that lets me stack several animations into a block and the end result is the same.


Regarding your comment about optimisations, can you provide a link where this is documented? The directives I have in my original code are clear in that the last (fourth) animation block should be delayed until after the third one completes. Therefore, the "optimisation" that the engine is doing that is causing the unintended effect is actually incorrect and I would argue that my code should work...unless the documentation clearly says this is not supported.

You say it should work, but it doesn't ! You may fight against the system, but that's not the best approach.


The proposed solution is not a code fix, it is the correct approach.


the last (fourth) animation block should be delayed until after the third one completes.

No. That's not how it works, because animations are executed in other threads. So the animation is called immediately, but the effect starts with delay: so they are passed immediately to the animation engine.


More precisely, what happens here is that you launch the first animation in a thread.

Then you immediately jump to next instruction, without waiting for the end, and launch the second, etc…

You can check it by adding a print("some message") at the beginning of each animation.

The 4 prints show immediately on log, without delay.


So, the last animation (all fade out) is called at the same time than the others. Then the animation engine sees 2 orders (with delays, but that's another point) to apply to the same object. I don't know how exactly it handles them (I guess it concatenates animations on each object, that's what I called optimization), but there is clearly a problem of overlapping contradictory statements.

This does not occur for labels 1 and 2, because animation do not apply to the same object.


If you want animation to occur in sequence, use completion block that guarantee that the next action is executed when animation is finished


Conclusion: the use of completion blocks is recommended practice:

https://stackoverflow.com/questions/10019696/ios-multiple-animation-blocks


You can file a bug report if you like, but search doc before.

I understand how threading works and that each animation block is threaded separately.

I agree that there are workarounds and better ways for what I'm trying to do (completion blocks or keyframes) but I don't agree the code I proposed to use is plain wrong and shouldn't work. Yes, the engine interprets my code as "overlapping contradictory" and negates the first 3 animations but logically it should work...there is no overlap or conflict in terms of time-based sequencing which is why I'm questioning its legitimacy. Without documentation that says "don't do this or else you'll have this effect", developers are left to their own interpretation. If the engine is not designed to do what I'm proposing it does, then fine...I can live with adjusting my code. But the documentation needs more detail and is currently lacking. I'm looking for a higher standard of developer documentation or a "smarter" engine...not just from Apple, but from all vendors.

I appreciate the discussion...I have filed a bug report and I'll see what comes out of that.

I may agree with you that documentation should be imporved (not only this one). Thanks to give feedback on the answer you get.


However, what I try to explain is that concatenation does not take into account the delay you set. It just concatenate 2 animations on the same object. You may regret it, but that's it. Your code may be logical but not correct. That's why it is advised everywhere to use completion.


BTW, there is a similar situation when you deal with transform, if you've ever tried.

A bit confusing at the beginning.