views:

783

answers:

3

I have an class "Cube" and in there is a method -rotate. The method sets the anchorPoint at the bottom left and then rotates the cube by 90 degrees. After that, another method is called, which makes the cube to rotate back by -90 degrees. That's done specifying the stop selector. Btw: This has no special purpose. Just learning!

-rotate gets called upon an touch event. But now, when the cube object is still rotating, and let's say it's currently at 33.943..., the user might make another touch event before that animation has finished.

This touch event would then immediately invoke again the -rotate method. What happens is, that the transformations overlap and the cube image gets stretched and deformed totally.

So before the new animation begins, the currently running animation has to stop.

UPDATE

I call this, before I kick off the animation:

- (void)resetAnimation {
    self.layer.transform = CATransform3DIdentity; // reset the transform to the identity transform ("no rotation applied, please")
    [Cube setAnimationDelegate:nil];
    [Cube setAnimationDidStopSelector:NULL];
}

Also, I set [Cube setAnimationBeginsFromCurrentState:YES]; everywhere where I begin an animation block.

So, when an animation is inited from the outside, this will look like this: (Note, that there are 5 objects of this class on the screen, which can be kicked to animate)

- (void)kickAnimation { // if that's called, the cube gets a kick to rotate
    self.layer.transform = CATransform3DIdentity;
    currentDegrees = 90; // just testing! it may also be negative
    NSString *animID = [NSString stringWithFormat:@"cube_%i",self.datasetID];
    if (currentDegrees > 0) {
     [self rotateRight:animID finished:0 context:self];
    } else if (currentDegrees < 0) {
     [self rotateLeft:animID finished:0 context:self];
    }
}

- (void)rotateRight:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
    // 1) here the anchorPoint is set to the lower right corner
    // 2) here the view's frame is repositioned so that the cube doesn't move visually after the anchorPoint changed
    [self rotateByDegrees:currentDegrees animationID:animationID];
}

- (void)rotateByDegrees:(CGFloat)degrees animationID:(NSString *)animationID {
    [Cube beginAnimations:animationID context:self];
    [Cube setAnimationDelegate:self];
    [Cube setAnimationDidStopSelector:@selector(rotateBack:finished:context:)];
    [Cube setAnimationDuration:2.1f];
    [Cube setAnimationBeginsFromCurrentState:YES];
    CATransform3D rotatedTransform = self.layer.transform;
    rotatedTransform = CATransform3DRotate(rotatedTransform, degrees * M_PI / 180.0, 0.0f, 0.0f, 1.0f);
    self.layer.transform = rotatedTransform;
    [UIView commitAnimations];
}

- (void)rotateBack:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
    currentDegrees *= -1;
    [Cube beginAnimations:animationID context:self];
    self.layer.transform = CATransform3DIdentity;
    [Cube setAnimationDelegate:nil];
    [Cube setAnimationDidStopSelector:NULL];
    [Cube setAnimationDuration:2.1f];
    [Cube setAnimationBeginsFromCurrentState:YES];
    CATransform3D rotatedTransform = self.layer.transform;
    rotatedTransform = CATransform3DRotate(rotatedTransform, currentDegrees * M_PI / 180.0, 0.0f, 0.0f, 1.0f);
    self.layer.transform = rotatedTransform;
    currentDegrees = 0;
    [UIView commitAnimations];
}

Now it seems to almost work. The cube doesn't deform anymore if animation-kickoffs overlap. But one thing stil is wrong: If an new "kick" comes in, and the cube is currently rotating back, it should imediately start rotating to right. But it happens that it first rotates back and then recognizes that "kick", which looks pretty queer ;)

+2  A: 

How are you applying the transform? I'm picturing something like this:

- (void)rotate {
    [UIView beginAnimations:@"rotate" context:nil];
    self.transform = [self nextTargetTransform];
    [UIView commitAnimations];
}

I haven't coded it up for your cube example, but that should work without the problems you're seeing. beginAnimations:context: should recognize that you're already transforming, and create a new goal state that smoothly completes the transform. Is that not happening?

Like I said, I haven't tested the above; one possible hiccup is that the animation will suddenly jump back to the original setting and start again. If that happens, post a comment and a little more of the code and we'll work out the fix for that one.

Rob Napier
No, it seams that this does not happen. I found a point in code where I can set it to the identity transform. I just didnt put it in the right place. I have a method that sets the anchorPoint and right after that corrects the shift of the frame, so that the view does not move. this caused problems. There I set the identity transform now before I change the anchorPoint, and it works. Although not perfectly. I updated my code above with how I set the transform.
Thanks
Looking at your code I see a couple of things. Most importantly, you set the layer transform in one place and the view transform in another place. Those are different transforms. You should pick a thing to transform and stick with it. You haven't shown how you perform the animation itself; do you have a -beginAnimations: block somewhere? Generally you should be starting an animation block, setting your current goal state, and then committing the animation. I'd probably keep the goal in a separate model variable rather than constantly re-transforming the transform, but both can work.
Rob Napier
One other thing: since you're messing with the layers, note the difference between the modelLayer and the presentationLayer. One is the goal state and the other is what's currently on the screen (where you are in the animation).
Rob Napier
Thanks Rob. I updated my post with more code. I tried to set self.layer.transform = CGAffineTransformIdentity, but that only worked for self.transform. But I figured out now that I could do self.layer.transform = CATransform3DIdentity. So would you recommend to animate on self.layer.transform rather than on self.transform? How would it look like, when I catch the goal state in a model variable? What exactly is the "goal state"?
Thanks
For 3D animations, you want to be transforming the layer rather than the view. CALayer has much more powerful transforms and animations than UIView. You should read up the Core Animation Programming Guide: http://developer.apple.com/iphone/library/documentation/Cocoa/Conceptual/CoreAnimation_guide/Introduction/Introduction.html. My use of "goal" state is a bit non-standard; I mean that in CA, you set a property (your "goal") and CA does all the work to get there. You generally don't have to worry about where you were if you change it. CA will recalculate a path to the new goal.
Rob Napier
+4  A: 

Here's a similar question and answer.

If you call [UIView setAnimationBeginsFromCurrentState:YES]; the new animation will begin from where the view is and stop the previous animation.

pgb
+2  A: 

If you truly want to stop all animations and then restart with your above methods, then call:

[self.layer removeAllAnimations];
mahboudz