Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animating CAGradientLayer's locations property in a UIScrollView mask resets after completion

I'm trying to animate the locations of a CAGradientLayer to shrink when the UIScrollView has reached a certain position. I have my gradient set up as a mask of the UIScrollView, and adjust the gradient's locations 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 CABasicAnimation 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.

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.

Here's my code (the relevant parts, anyway):

- (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;
}
like image 655
Andrew R. Avatar asked Feb 20 '23 03:02

Andrew R.


1 Answers

Adding an animation to a layer never changes the layer's properties. It just animates the layer's appearance on screen. Once the animation is removed from the layer, the layer is redrawn based on its properties. So you need to change the layer's properties, then apply the animation. In your if statement, do this:

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"locations"];
animation.fromValue = gradientMask.locations;
animation.toValue = locations;
gradientMask.locations = locations;
[gradientMask addAnimation:animation forKey:animation.keyPath];

For a better understanding, watch the “Core Animation Essentials” video from WWDC 2011. It is very helpful.

like image 162
rob mayoff Avatar answered Apr 29 '23 18:04

rob mayoff