Search code examples
objective-ccakeyframeanimation

Simple keyFrame animation using addKeyframeWithRelativeStartTime is jittery


I've created the following keyFrame animation; constant is in this case a CGPoint x coordinate of a UIView (this animation moves the object up and down, repeating, supposedly smoothly.

When I set totalDuration to 2.0, it works fine, but when I set totalDuration to 1.5 or 1.6, it goes all jittery - as if the UIView doesn't have time to return to it's original place before the animation begins again.

Clearly my math is wrong here, but I just can't put my finger on where.

- (void)linearAnimation:(CGFloat)constant imageView:(UIImageView*)imageView animationStyle:(RSGesturesTutorialAnimationStyle)animationStyle{
    CGFloat frameDuration;
    CGFloat totalDuration = 1.6; // setting this to 2.0 animates smoothly, 1.5/6 do not.

    NSArray* keyFrames = @[
                          [NSNumber numberWithFloat:constant - 100],
                          [NSNumber numberWithFloat:constant],
                          [NSNumber numberWithFloat:constant + 100],
                          [NSNumber numberWithFloat:constant]
                          ];

    frameDuration = totalDuration/keyFrames.count;

    [UIView animateKeyframesWithDuration:totalDuration
                                   delay:0
                                 options: UIViewKeyframeAnimationOptionCalculationModeLinear | UIViewKeyframeAnimationOptionRepeat
                              animations:^{
                                  [keyFrames enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
                                      [UIView addKeyframeWithRelativeStartTime:idx*frameDuration relativeDuration:frameDuration animations:^{
                                          imageView.center = CGPointMake(imageView.center.x, [obj floatValue]);
                                      }];
                                  }];
                              } completion:^(BOOL finished) {
                              }];
}

Solution

  • Something like this should work:

    - (void)linearAnimation:(CGFloat)constant imageView:(UIImageView *)imageView {
        NSTimeInterval totalDuration = 1.6; // setting this to 2.0 animates smoothly, 1.5/6 do not.
        CGFloat startY = imageView.center.y;
    
        NSArray* keyFrames = @[
                             @(startY - constant),
                             @(startY),
                             @(startY + constant),
                             @(startY)
                             ];
    
        CGFloat period = 1.0f / keyFrames.count;
    
        [UIView animateKeyframesWithDuration:totalDuration
                                   delay:0
                                 options: UIViewKeyframeAnimationOptionCalculationModeLinear | UIViewKeyframeAnimationOptionRepeat
                              animations:^{
    
                                  [keyFrames enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    
                                      NSTimeInterval interval = idx * period;
    
                                      [UIView addKeyframeWithRelativeStartTime:interval relativeDuration:period animations:^{
    
                                          imageView.center = CGPointMake(imageView.center.x, [obj floatValue]);
                                      }];
                                  }];
                              } completion:^(BOOL finished) {
                              }];
    }
    

    When adding keyframes to an animation, the relativeStartTime and relativeDuration values are not absolute values, they need to be a percentage of the overall animation. This is so you can change the totalDuration, without having to change many duration values.

    From the doc: (frameStartTime) The time at which to start the specified animations. This value must be in the range 0 to 1, where 0 represents the start of the overall animation and 1 represents the end of the overall animation. For example, for an animation that is two seconds in duration, specifying a start time of 0.5 causes the animations to begin executing one second after the start of the overall animation.