Search code examples
iosrubycocoa-touchcore-animationrubymotion

CALayer opacity animation works but bounds animation doesn't


I'm trying to animate the bounds of a CALayer and it's not working. Here's the code:

class CircleView < UIIVew

  def initWithFrame(frame)
    super

    # determine the dimensions
    radius = (bounds.size.width < bounds.size.height ? bounds.size.width : bounds.size.height) / 2

    # create the circle layer
    @circle = CAShapeLayer.layer
    @circle.bounds = bounds
    @circle.path = UIBezierPath.bezierPathWithRoundedRect(bounds, cornerRadius: radius).CGPath

    @circle.fillColor = UIColor.blueColor.CGColor

    # add the circle to the view
    self.layer.addSublayer(@circle)

    shrink

    self
  end

  private

  # Applies the shrink animation to the provided circle layer.
  def shrink

    # determine the bounds
    old_bounds = @circle.bounds
    new_bounds = CGRectMake(old_bounds.size.width / 2, old_bounds.size.height / 2, 0, 0)

    # animate the shrinking
    animation = CABasicAnimation.animationWithKeyPath("bounds")
    animation.fromValue = NSValue.valueWithCGRect(old_bounds)
    animation.toValue = NSValue.valueWithCGRect(new_bounds)
    animation.duration = 3.0
    @circle.addAnimation(animation, forKey:"bounds")

    # set the bounds so the animation doesn't bounce back when it's complete
    @circle.bounds = new_bounds
  end

  # Applies the shrink animation to the provided circle layer.
  def disappear

    # animate the shirnk
    animation = CABasicAnimation.animationWithKeyPath("opacity")
    animation.fromValue = NSNumber.numberWithFloat(1.0)
    animation.toValue = NSNumber.numberWithFloat(0.0)
    animation.duration = 3.0
    @circle.addAnimation(animation, forKey:"opacity")

    # set the value so the animation doesn't bound back when it's completed
    @circle.opacity = 0.0
  end
end

If I call disappear from the initWithFrame method, the animation works fine. However, calling shrink does nothing. What am I doing wrong?


Solution

  • That's because you are using a CAShapeLayer. The path is independent of the bounds, so you have to animate it separately.

    I could reproduce your problem with a version of your code roughly translated to Objective C. When I changed the shrinkmethod to this, it worked:

    -(void)shrink
    {
      CGRect old_bounds = self.circle.bounds;
      CGRect new_bounds = CGRectMake(old_bounds.size.width / 2, old_bounds.size.height / 2, 0, 0);
    
      // bounds
      CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath: @"bounds"];
      animation.fromValue = [NSValue valueWithCGRect: old_bounds];
      animation.toValue = [NSValue valueWithCGRect: new_bounds];
      animation.duration = 3.0;
      [self.circle addAnimation: animation forKey: @"bounds"];
    
      // path
      CGPathRef old_path = self.circle.path;
      CGPathRef new_path = [UIBezierPath bezierPathWithRoundedRect:new_bounds cornerRadius:0].CGPath;
    
      CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath: @"path"];
      pathAnimation.fromValue = (__bridge id)(old_path);
      pathAnimation.toValue = (__bridge id)(new_path);
      pathAnimation.duration = 3.0;
      [self.circle addAnimation: pathAnimation forKey: @"path"];
    
      //set the value so the animation doesn't bound back when it's completed
      self.circle.bounds = new_bounds;
      self.circle.path = new_path;
    }
    

    Technically, you might not even have to animate the bounds. If you set a distinct backgroundColor on your circle layer, you can experiment a little and more easily see how boundsand path can be set and animated more or less independently (for example, in your original code you could see that the bounds are in fact animated but the path stays the same).