3

Re-asking the question:

When you add an animation for the contents key, a CATransitionAnimation is apparently being triggered that fades the original contents property to the first value in the animation's values array, resulting in a .25 second fade. And it looks bad! I have suppressed every animatable property using all the methods discussed here (returning null animations through a delegate, into the actions dictionary, using CATransaction), but none of these seem to be targeting this particular transition animation.

alt text

I have been looking into what property could possibly be responsible for this, but cannot figure it out.

I need to suppress the transition animation that is occurring when you add an animation to the contents key.

As I'm at such a loss, I will put the keyframe animation that is being added for you to see. I figure maybe I am doing something wrong here? Just a note, that array is just an array of 6 CGImageRefs (the frames of the animation).

+ (CAKeyframeAnimation *)moveLeftAnimation {

CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"contents"];
animation.values = [NSArray arrayWithArray:[Setzer walkingLeftSprite]];
animation.duration = 0.5f;
animation.keyTimes = [NSArray arrayWithObjects:
                      [NSNumber numberWithFloat:0.0], 
                      [NSNumber numberWithFloat:0.2], 
                      [NSNumber numberWithFloat:0.4],
                      [NSNumber numberWithFloat:0.6],
                      [NSNumber numberWithFloat:0.8],
                      [NSNumber numberWithFloat:1.0],
                      nil];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
return animation;

}

Also, I have an animation that handles the position key, in the sprite's action dictionary:

+ (CABasicAnimation *)moveAnimation {

CABasicAnimation *moveAnimation = [CABasicAnimation animation];
moveAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
moveAnimation.duration = 0.5f;

return moveAnimation;

}

I am thinking maybe this transition is occurring when you change the layer's position? I don't know...

Please help! This is driving me NUTS!

Alec Sloman
  • 699
  • 4
  • 18
  • +1 for gratuitous use of Setzer between this and the previous post – Matt Sieker Dec 07 '10 at 08:04
  • I have done a transaction on both animations I add (the keyframe animation I add explicitly, and there is one for the positions key in its actions dictionary that just makes the movement linear and also .5 seconds) but it doesn't suppress the CATransitionAnimation apparent in the above screenshot. That is the problem. Here is my try at simple english: "The keyframe animation itself iterates through the 6 CGImageRefs that I pull from the sprite's gif, over .5 seconds." Haha, sorry :( – Alec Sloman Dec 07 '10 at 09:17

4 Answers4

1

To prevent any animation, you could set an object as the delegate of your CALayer and then implement the ‑actionForLayer:forKey: delegate method and return a null object:

- (id<CAAction>)actionForLayer:(CALayer*)layer forKey:(NSString*)key 
{
    if(layer == yourLayer)
    {
        if([key isEqualToString:@"contents"])
        {
            return (id<CAAction>)[NSNull null];
        }
    }
    return nil;
}
Rob Keniger
  • 45,830
  • 6
  • 101
  • 134
  • I'll give this a shot. This won't interfere with adding animations explicitly? – Alec Sloman Dec 02 '10 at 12:05
  • No, it shouldn't. If you need more control over which layers or in what states you want no animation, just add logic to the delegate method. – Rob Keniger Dec 02 '10 at 22:19
  • It still interpolates between the original contents property and frame 1 of the animation. I have tried adding a null animation in a delegate, and in the actions dictionary. Nothing works. – Alec Sloman Dec 03 '10 at 01:25
  • I have up-voted all the answers and re-wrote the question with code and an image. Would you take another look and see if my question makes more sense? – Alec Sloman Dec 07 '10 at 07:45
1

You can do something along the lines of what I describe in this answer, where I disable the implicit animations for various layer properties by setting the appropriate values in the actions dictionary on that layer.

In your case, I believe something like

NSMutableDictionary *newActions = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
                                   [NSNull null], @"contents",
                                   nil];
layer.actions = newActions;
[newActions release];

should prevent the implicit animation of a layer's contents property until you explicitly animate it.

Community
  • 1
  • 1
Brad Larson
  • 170,088
  • 45
  • 397
  • 571
  • I have up-voted all the answers and re-wrote the question with code and an image. Would you take another look and see if my question makes more sense? – Alec Sloman Dec 07 '10 at 07:46
  • @Alec - You're explicitly animating the `contents` property in your first CAKeyframeAnimation using a linear timing function. This will override any of the approaches discussed here, which all suppress implicit animations only (except Joe's first suggestion, which will disable all animations of any type). The result will be a linear crossfade between the various images. – Brad Larson Dec 07 '10 at 15:00
  • @Alec - In order to achieve the effect you want, you'll either need to go with an array of `animationImages` in a UIImageView, like Joe suggests, or create a manual timing function that swaps out the contents of a layer at regular intervals (combined with the actions dictionary approach I show above). – Brad Larson Dec 07 '10 at 15:03
  • I'll give the manual timing function a shot. btw I do accept answers, eventually :D I appreciate everyone's help with this. CoreAnimation is simple on first sight, because so much goes on behind the scenes. – Alec Sloman Dec 08 '10 at 00:14
  • @Alec - Yes, Core Animation gives you a lot for free, and even holds up reasonably well on edge cases like this. Thinking about it, there might still be a way to tie into Core Animation's timing mechanism to do this, like Tim Wood suggests here: http://www.omnigroup.com/blog/entry/Animating_CALayer_content/ , but I haven't done something like this yet myself. – Brad Larson Dec 08 '10 at 04:19
  • I did it with a regular CAKeyFrameAnimation. I will explain in more detail tomorrow. I am in S. Korea and it's getting pretty late! – Alec Sloman Dec 08 '10 at 13:28
1

Here are a few notes on how this puzzle was solved, and how everyone's answers provided a piece of the puzzle.

To restate the problem: when I added a keyframe animation to the @contents key of a CALayer, there appeared to be a .25 second fade transition between the original contents property and the first frame of the keyframe animation. This looked bad, and I wanted to get rid of it.

At first, I thought surely that by using a CATransaction I could suppress this implicit transition animation, as that is what Apple's docs lead you to believe. Using a transaction, I suppressed in every possible way you could imagine, and yet it was still happening. Then I tried returning NULL animations for every animatable property via a dictionary. No luck. Then I did the same thing, but with a delegate. Still no luck.

What I didn't mention is that at the same time the animation was being added and the layer was being moved, two sublayers beneath it were being removed from the their superlayers. I tried adding custom animations for the onOrderOut key, but to no avail. Then I stumbled upon another question here on StackOverflow, about adding a custom animation for the onOrderOut key. It turns out, quite simply, that you can't, and that if you wan to implement some other animation for when a sublayer is removed, you have to use the delagate method animationDidStop. How can I use custom animations for onOrderOut in Core Animation?

So at this point I was convinced that this ghost image had nothing to do with the actual layer in question, the sprite itself. To test this, I just didn't add the sublayers that went beneath it. Sure enough, there was no lingering ghost when I moved the sprite around. It looked perfect. Then I added the layers beneath there was the ghost. It was almost like the sprite's contents were drawn into the layers beneath it, so that when they were removed, there was a sort of imprint.

Instead of removing the sublayers, I just tried hiding them. Bingo. It was perfect. The same fade transition occurred, but there was no imprint of the sprite left. I still don't understand why this is so.

Then, because I still needed to remove those layers, I implemented the animationDidStop delegate method for the sprite's various movement animations to remove them.

This is the original:

alt text

This is the new version:

alt text

So while I don't understand, technically, why there appears to be an imprint, I am all but certain that it concerns what goes on behind the scenes when you remove a sublayer. Also, for interest sake, I still wanted that sublayer to be hidden on animation start, so I just set it to hidden and provided my own transition animation.

So thanks to everyone for their help. This is a strange use case, I know, but if you are ever thinking of making a 2d sprites-based Final Fantasy Tactics ripoff, then hopefully my pain will be to your benefit!

Community
  • 1
  • 1
Alec Sloman
  • 699
  • 4
  • 18
0
class AnimationLayer: CALayer {
    override class func defaultAction(forKey event: String) -> CAAction? {
        let transition = CATransition()
        transition.duration = 0
        return transition
    }
}