title: Animating CAGradientLayer's locations property in a UIScrollView mask resets after completion
body: <p>I'm trying to animate the locations of a <code>CAGradientLayer</code> to shrink when the <code>UIScrollView</code> has reached a certain position. I have my gradient set up as a mask of the <code>UIScrollView</code>, and adjust the gradient's <code>locations</code> property to expand a bit as the scroll view scrolls. This works just fine for me, except when I try to animate a change to the locations. When I perform the animation - either as an explicit <code>CABasicAnimation</code> or implicitly - the animation performs correctly, but for whatever reason, once the animation is complete it then changes the locations of my gradient (without animation) to some other, non-zero start and end points. It uses the same values every single time to do this, and those values are not specified anywhere in my code.</p>
  
  <p>I'm fairly new to Core Animation, so maybe I'm missing something, but as far as I can tell my code is correct. I've also tried waiting to perform the animation, to account for any timing issues with my scrolling, but that didn't help either.</p>
  
  <p>Here's my code (the relevant parts, anyway):</p>
  
  <pre><code>- (void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
      [CATransaction begin];
      [CATransaction setDisableActions:YES];
      gradientMask.position = CGPointMake(currentPageOffset, 0.0f);
      gradientMask.bounds = self.scrollView.bounds;
      [CATransaction commit];
  
      _animateGradientShrinkOnScroll = YES;
  
      // Reset scrollView contentOffset (I'm doing infinite paging)
      ...
  }
  
  - (void) scrollViewDidScroll:(UIScrollView *)scrollView {
      CGFloat offset = self.scrollView.contentOffset.x;
      CGFloat relativeOffset = offset - currentPageOffset;
  
      [CATransaction begin];
      [CATransaction setDisableActions:YES];
      gradientMask.position = self.scrollView.contentOffset;
      if (_animateGradientShrinkOnScroll)
          [CATransaction commit]; // if animating, set position w/o animation first
  
      CGFloat leftLength = ABS(relativeOffset);
      CGFloat rightLength = ABS(relativeOffset);
  
      CGFloat maxLength = [self maxGradientLength];
      leftLength = MIN((leftLength / 2), maxLength);
      rightLength = MIN((rightLength / 2), maxLength);
  
      // When animating, this effectively always goes from non-zero lengths to zero lengths,
      // and I've verified this by logging the values used.
      [self setGradientLeftLength:leftLength rightLength:rightLength animated:_animateGradientShrinkOnScroll];
  
      if (!_animateGradientShrinkOnScroll)
          [CATransaction commit];
      _animateGradientShrinkOnScroll = NO;
  }
  
  - (void) setGradientLeftLength:(CGFloat)leftLength rightLength:(CGFloat)rightLength animated:(BOOL)animated {
      CGFloat startRatio = MAX((leftLength / gradientMask.bounds.size.width), 0);
      CGFloat endRatio = 1.0f - MAX((rightLength / gradientMask.bounds.size.width), 0);
  
      NSArray *locations = [NSArray arrayWithObjects:
                            [NSNumber numberWithFloat:0.0f],
                            [NSNumber numberWithFloat:startRatio],
                            [NSNumber numberWithFloat:endRatio],
                            [NSNumber numberWithFloat:1.0f], nil];
  
      if (animated) {
          CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"locations"];
          animation.toValue = locations;
          [gradientMask addAnimation:animation forKey:kGradientMaskAnimationShrink];
      }
      else
          gradientMask.locations = locations;
  }
  </code></pre>