Domanda

I've made a wrapper for compact creation of CABasicAnimation instances. It's implemented through a category for UIView as an instance method named change:from:to:in:ease:delay:done:. So for example, I can do:

[self.logo
  change:@"y"
  from:nil            // Use current self.logo.layer.position.y
  to:@80
  in:1                // Finish in 1000 ms
  ease:@"easeOutQuad" // A selector for a CAMediaTimingFunction category method
  delay:0
  done:nil];

The problem

When the CABasicAnimation starts, animationDidStart: handles setting self.logo.layer.position.y to 80 (the end value). Before it worked like this, I tried using animationDidStop:finished: to do the same thing, but found the layer flickering after completing the animation. Now, the layer goes straight to the end value, and no interpolation occurs. I implemented animationDidStart: in my UIView category like so:

- (void)animationDidStart:(CAAnimation *)animation
{
    [self.layer
        setValue:[animation valueForKey:@"toValue"]
        forKeyPath:[animation valueForKey:@"keyPath"]];
}

I'm setting the end value in order to match the model layer with the presentation layer (in other words, to prevent resetting back to the start position).

Here's the implementation to change:from:to:in:ease:delay:done:...

- (CABasicAnimation*) change:(NSString*)propertyPath
                        from:(id)from
                          to:(id)to
                          in:(CGFloat)seconds
                        ease:(NSString*)easeName
                       delay:(CGFloat)delay
                        done:(OnDoneCallback)done
{
  NSString* keyPath = [app.CALayerAnimationKeyPaths objectForKey:propertyPath];
  if (keyPath == nil) keyPath = propertyPath;
  CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:keyPath];
  if (delay > 0) animation.beginTime = CACurrentMediaTime() + delay;
  if (easeName != nil) animation.timingFunction = [CAMediaTimingFunction performSelector:NSSelectorFromString(easeName)];
  if (from != nil) animation.fromValue = from;
  animation.toValue = to;
  animation.duration = seconds;
  animation.delegate = self;
  [self.layer setValue:done forKey:@"onDone"];
  [self.layer addAnimation:animation forKey:keyPath];
  return animation;
}

In the first line of the above code, this is the NSDictionary I use to convert property shortcuts to the real keyPath. This is so I can just type @"y" instead of @"position.y" every time.

app.CALayerAnimationKeyPaths = @{
  @"scale": @"transform.scale",
  @"y": @"position.y",
  @"x": @"position.x",
  @"width": @"frame.size.width",
  @"height": @"frame.size.height",
  @"alpha": @"opacity",
  @"rotate": @"transform.rotation"
};

Any questions?

È stato utile?

Soluzione

What you're seeing is, I think, expected behavior. You are setting an animatable property after animation of that property has started. That is in fact the most common and recommended way to do exactly what you are seeing happen, i.e. cancel the animation and jump right to the final position now. So you are deliberately canceling your own animation the minute it gets going.

If that's not what you want, don't do that. Just set the animated property to its final value before the animation is attached to the layer - being careful, of course, not to trigger implicit animation as you do so.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top