Animate layer on animated UIView

2019-05-10 12:42发布

问题:

I'm having trouble animating a layer on one of my views. I have googled the issue, but only find answers using CATransaction which assumes that I know the fromValue and toValue of its bounds. I have a view in a tableHeader that resizes itself when clicked. This view is an ordinary UIView and animates just as expected in an UIView.animate()-block. This view has a CAGradientLayer as a sublayer, to give it a gradient backgroundcolor. When the view animates its height, the layer does not animate with it. The layer changes its bounds immediately when the animation starts.

To make sure the layer gets the right size overall (during init/loading/screen rotation etc.) I have been told to do this:

override func layoutSubviews() {
    super.layoutSubviews()
    gradientLayer.frame = backgroundView.bounds
}

It gets the right size every time, but it never animates to it.

To do my view-animation, I do this:

self.someLabelHeightConstraint.constant = someHeight
UIView.animate(withDuration: 0.3, animations: { [weak self] in                
    self?.layoutIfNeeded()
})

which works perfectly, but I assume layoutIfNeeded() calls layoutSubviews at some point, which I assume will ruin any CALayer-animations I add into the block.

As you can see, I only change the constant of a constraint set on a view inside my view, so I actually don't know what the size of the actual header-view will be when the animation is completed. I could probably do some math to figure out what it'll be, but that seems unnecessary..

Are there no better ways to do this?

回答1:

There are kludgy ways to update a sublayer's frame during an animation, but the most elegant solution is to let UIKit take care of this for you. Create a subclass UIView whose layerClass is a CAGradientLayer. When the view is created, the CAGradientLayer will be created for you. And when you animate the view's frame, the gradient layer is animated gracefully for you.

@IBDesignable
public class GradientView: UIView {

    @IBInspectable public var startColor: UIColor = .white { didSet { updateColors() } }
    @IBInspectable public var endColor:   UIColor = .black { didSet { updateColors() } }

    override open class var layerClass: AnyClass { return CAGradientLayer.self }

    override public init(frame: CGRect = .zero) {
        super.init(frame: frame)
        config()
    }

    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        config()
    }

    private func config() {
        updateColors()
    }

    private func updateColors() {
        let gradientLayer = layer as! CAGradientLayer

        gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
    }

}

Note, I've made this @IBDesignable so you can put it in a framework target and then use it in IB, but that's up to you. That's not required. The main issue is the overriding of layerClass so that UIKit takes care of the animation of the layer as it animates the view.



回答2:

You need to remove the actions of the CALayer

Add this code

gradientLayer.actions = ["position": NSNull(),"frame":NSNull(),"bounds":NSNull()]