Henny Lee
Henny Lee

Reputation: 3062

Having custom CAShapeLayer animate with the Button

I'm animating my button by changing a constraint of my auto layout and using an UIView animation block to animate it:

UIView.animateWithDuration(0.5, animations: { self.layoutIfNeeded() })

In this animation, only the width of the button is changing and the button itself is animating.

In my button, there's a custom CAShapeLayer. Is it possible to catch the animation of the button and add it to the layer so it animates together with the button?

What I've Tried:

// In my CustomButton class
override func actionForLayer(layer: CALayer, forKey event: String) -> CAAction? {

    if event == "bounds" {
        if let action = super.actionForLayer(layer, forKey: "bounds") as? CABasicAnimation {
        let animation = CABasicAnimation(keyPath: event)
        animation.fromValue = border.path
        animation.toValue = UIBezierPath(rect: bounds).CGPath

        // Copy values from existing action

        border.addAnimation(animation, forKey: nil) // border is my CAShapeLayer
    }

    return super.actionForLayer(layer, forKey: event)
}

// In my CustomButton class
override func layoutSubviews() {
    super.layoutSubviews()

    border.frame = layer.bounds

    let fromValue = border.path
    let toValue = UIBezierPath(rect: bounds).CGPath

    CATransaction.setDisableActions(true)
    border.path = toValue

    let animation = CABasicAnimation(keyPath: "path")
    animation.fromValue = fromValue
    animation.toValue = toValue
    animation.duration = 0.5

    border.addAnimation(animation, forKey: "animation")
}

Nothing is working, and I've been struggling for days..


CustomButton:

class CustomButton: UIButton {

    let border = CAShapeLayer()

    init() {
        super.init(frame: CGRectZero)

        translatesAutoresizingMaskIntoConstraints = false
        border.fillColor = UIColor.redColor().CGColor
        layer.insertSublayer(border, atIndex: 0)
    }

//  override func layoutSubviews() {
//      super.layoutSubviews()
//      
//      border.frame = layer.bounds
//      border.path = UIBezierPath(rect: bounds).CGPath
//  }

    override func layoutSublayersOfLayer(layer: CALayer) {
        super.layoutSublayersOfLayer(layer)

        border.frame = self.bounds
        border.path = UIBezierPath(rect: bounds).CGPath
    }
}

Upvotes: 0

Views: 776

Answers (1)

Jelly
Jelly

Reputation: 4532

All you have to do is resize also the sublayers when the backing layer of your view is resized. Because of implicit animation the change should be animated. So all you need to do is basically to set this in you custom view class:

override func layoutSublayersOfLayer(layer: CALayer!) {
    super.layoutSublayersOfLayer(layer)
    border.frame = self.bounds
}

Updated

I had some time to play with the animation and it seems to work for me now. This is how it looks like:

class TestView: UIView {

    let border = CAShapeLayer()

    init() {
        super.init(frame: CGRectZero)

        translatesAutoresizingMaskIntoConstraints = false
        border.backgroundColor = UIColor.redColor().CGColor
        layer.insertSublayer(border, atIndex: 0)
        border.frame = CGRect(x: 0, y:0, width: 60, height: 60)

        backgroundColor = UIColor.greenColor()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }


    override func layoutSublayersOfLayer(layer: CALayer) {
        super.layoutSublayersOfLayer(layer)

        CATransaction.begin();
        CATransaction.setAnimationDuration(10.0);
        border.frame.size.width = self.bounds.size.width
        CATransaction.commit();
    }

}

And I use it like this:

var tview: TestView? = nil
override func viewDidLoad() {
    super.viewDidLoad()

    tview = TestView();
    tview!.frame = CGRect(x: 100, y: 100, width: 60, height: 60)
    view.addSubview(tview!)
}

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)

    self.tview!.frame.size.width = 200
}

The issue is that the frame property of CALayer is not animable. The docs say:

Note:Note The frame property is not directly animatable. Instead you should animate the appropriate combination of the bounds, anchorPoint and position properties to achieve the desired result.

If you didn't solve your problem yet or for others that might have it I hope this helps.

Upvotes: 1

Related Questions