Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animate `backgroundColor` of a `UIView` that implements `drawRect`

I have a custom UIView and I would like to animate its backgroundColor property. This is an animatable property of a UIView.

This is the code:

class ETTimerUIView: UIView {
  required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
  }
  // other methods
  func flashBg() {
    UIView.animateWithDuration( 1.0, animations: {
      self.backgroundColor = UIColor.colorYellow()
    })
  }
  override func drawRect() {
    // Something related to a timer I'm rendering
  }

This code causes causes the animation to skip and the color to change immediately:

self.backgroundColor = UIColor.colorYellow() // Changes immediately to yellow

If I animate alpha, this animates from 1 to 0 over one second as expected:

self.alpha = 0 // animates

How do I animate a background color change in this situation?

  • Implementing drawRect blocks backgroundColor animation, but no answer is provided yet.
  • Maybe this is why you can't combine drawRect and animateWithDuration, but I don't understand it much.

I guess I need to make a separate view--should this go in the storyboard in the same view controller? programmatically created?

Sorry, I'm really new to iOS and Swift.

like image 867
SimplGy Avatar asked Mar 05 '15 11:03

SimplGy


3 Answers

It is indeed not working when I try it, I had a related question where putting the layoutIfNeeded() method inside the animation worked and made the view smoothly animating (move button towards target using constraints, no reaction?). But in this case, with the backgroundColor, it does not work. If someone knows the answer I will be interested to know.

But if you need a solution right now, you could create a UIView (programmatically or via the storyboard) that is used only as a container. Then you add 2 views inside : one on top, and one below, with the same frame as the container. And you only change the alpha of the top view, which let the user see the view behind :

class MyView : UIView {
    var top : UIView!

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.backgroundColor = UIColor.blueColor()

        top = UIView(frame: CGRectMake(0,0, self.frame.width, self.frame.height))
        top.backgroundColor = UIColor.yellowColor()
        self.addSubview(top)
    }

    override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
        let sub = UIView(frame: CGRectMake(0,0, self.frame.width, self.frame.height))
        sub.backgroundColor = UIColor.purpleColor()
        self.sendSubviewToBack(sub)
        UIView.animateWithDuration(1, animations: { () -> Void in
            self.top.alpha = 0
            }) { (success) -> Void in
                println("anim finished")
        }
    }
}
like image 144
Paul Avatar answered Nov 09 '22 17:11

Paul


The answer is that you cannot animate backgroundColor of a view that implements drawRect. I do not see docs for this anywhere (please comment if you know of one).

You can't animate it with animateWithDuration, nor with Core Animation.

This thread has the best explanation I've found yet:

When you implement -drawRect:, the background color of your view is then drawn into the associated CALayer, rather than just being set on the CALayer as a style property... thus prevents you from getting a contents crossfade

The solution, as @Paul points out, is to add another view above, behind, or wherever, and animate that. This animates just fine.

Would love a good understanding of why it is this way and why it silently swallows the animation instead of hollering.

like image 2
SimplGy Avatar answered Nov 09 '22 18:11

SimplGy


Not sure if this will work for you, but to animate the background color of a UIView I add this to a UIView extension:

extension UIView {

    /// Pulsates the color of the view background  to white.
    ///
    /// Set the number of times the animation should repeat, or pass
    /// in `Float.greatestFiniteMagnitude` to pulsate endlessly.
    /// For endless animations, you need to manually remove the animation.
    ///
    /// - Parameter count: The number of times to repeat the animation.
    ///
    func pulsate(withRepeatCount count: Float = 1) {

        let pulseAnimation = CABasicAnimation(keyPath: "backgroundColor")

        pulseAnimation.fromValue = <#source UIColor#>.cgColor
        pulseAnimation.toValue = <#target UIColor#>.cgcolor

        pulseAnimation.duration = 0.4
        pulseAnimation.autoreverses = true
        pulseAnimation.repeatCount = count

        pulseAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
        self.layer.add(pulseAnimation, forKey: "Pulsate")
        CATransaction.commit()
    }
}

When pasting this in to a source file in Xcode, replace the placeholders with your two desired colors. Or you can replace the entire lines with something like these values:

pulseAnimation.fromValue = backgroundColor?.cgColor
pulseAnimation.toValue = UIColor.white.cgColor
like image 1
Johan Avatar answered Nov 09 '22 17:11

Johan