Marko Zadravec Marko Zadravec - 2 months ago 9
Swift Question

Animate over properties in UIView using draw in rect

I have my custom view like this :

@IBDesignable
class MyTableViewCell: UITableViewCell {

@IBInspectable var cardColor : UIColor = UIColor.white
@IBInspectable var cardHorizontalPadding : Int = 20
@IBInspectable var cardVerticalPadding : Int = 20
@IBInspectable var cardCornerRadius : Int = 10
@IBInspectable var cardShadowRadius : Int = 6

var cardLayer : CAShapeLayer = CAShapeLayer()
var cardRect = CGRect.zero;

override func awakeFromNib() {
super.awakeFromNib()

}

override func draw(_ rect: CGRect) {

self.backgroundColor = UIColor.clear
print("Sublayers count is \(layer.sublayers?.count)")
let cardRect = rect.insetBy(dx: CGFloat(self.cardHorizontalPadding), dy: CGFloat(self.cardVerticalPadding))
let cardPath = UIBezierPath(roundedRect: cardRect, cornerRadius: CGFloat(self.cardCornerRadius)).cgPath

cardLayer.lineWidth = 0
cardLayer.fillColor = self.cardColor.cgColor

cardLayer.path = cardPath
cardLayer.shadowRadius = CGFloat(self.cardShadowRadius)
cardLayer.shadowOffset = CGSize.init(width: -0.2, height: -0.2)
cardLayer.shadowPath = cardPath
cardLayer.shadowOpacity = 0.2
layer.addSublayer(cardLayer)
layer.replaceSublayer(cardLayer, with: cardLayer)



}



override func setSelected(_ selected: Bool, animated: Bool) {


// Configure the view for the selected state
}

}


And I want to animate over
cardVerticalPadding
, like this

UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1/10, animations: {

currentCell.cardVerticalPadding = 10
})


and it is not working.

Answer

From UIView documentation:

The following properties of the UIView class are animatable:

  • frame
  • bounds
  • center
  • transform
  • alpha
  • backgroundColor

So the reason is that the UIKit animation framework is quite limited in the properties that it can manage. The solution is to manage your own animations using CADisplayLink to update your properties each time the display is refreshed. I've written a framework to make this super easy, it's available here: https://github.com/j-h-a/Animation (using the very permissive MIT License).

The API is a bit different than the UIKit animations, instead of setting the final value, your closure gets called repeatedly and you set whatever value you want at the time, based on a progress value that's passed in. Here is an example usage for your case:

import Animation

...

Animation.animate(identifier: "animateVerticalPadding", duration: 0.1,
    update: { progress in
        currentCell.cardVerticalPadding = 20.0 <~~ progress ~~> 10.0
    })

The <~~ and ~~> operators is my syntax for linear interpolation, that is equivalent to lerp(20.0, 10.0, progress). 20.0 is the initial value, 10.0 is the final value. If you wanted to use an ease-in-ease-out curve instead of linear, then you can do this:

currentCell.cardVerticalPadding = 20.0 <~~ Curve.easeInEaseOut[progress] ~~> 10.0

Alternatively, you can calculate the current value using whatever means you prefer, using progress (which increases from 0.0 to 1.0 over the duration) to calculate the current value you want. The interpolation and curves are just helpers.

Note: I haven't added it to cocoapods yet. I will edit out this note later once that is done.