2

I am building an iOS app and I need a way to reflect a rotating (by CABasicAnimation) image to the surface below, like a translucent material effect. Here is my code for the images named indicator and indicatorReflection to initialize:

#define rotation_reflected(ANG) CGAffineTransformMakeRotation(M_PI/2 - (ANG * M_PI / 180.0))
#define rotation(ANG) CGAffineTransformMakeRotation(-M_PI/2 - (ANG * M_PI / 180.0))
[self rotateIndicator:0];

-(void)rotateIndicator:(float)degrees{
    self.indicatorView.transform = rotation(degrees);
    self.indicatorReflectionView.transform = rotation_reflected(degrees);
}

I animate them using the following code, afterwards:

-(void)startWanderingIndicator{
    CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform"];
    CATransform3D xform = CATransform3DMakeAffineTransform(rotation(180));
    anim.toValue = [NSValue valueWithCATransform3D:xform];
    anim.duration = 4.0f;
    anim.fillMode = kCAFillModeForwards;
    anim.removedOnCompletion = NO;
    [self.indicatorView.layer addAnimation:anim forKey:@"rotation"];


    anim = [CABasicAnimation animationWithKeyPath:@"transform"];
    xform = CATransform3DMakeAffineTransform(rotation_reflected(180));
    anim.toValue = [NSValue valueWithCATransform3D:xform];
    anim.duration = 4.0f;
    anim.fillMode = kCAFillModeForwards;
    anim.removedOnCompletion = NO;
    [self.indicatorReflectionView.layer addAnimation:anim forKey:@"rotation"];

}

There is no problem with the first one. The problem begins with the reflected view. I've tried almost all the +/- PI/2 and +/- ANGLE combinations, but I can never make the reflected view to follow the correct path of reflection. I'm not a trigonometry guy, but this should be something very trivial to anyone who knows some math, and besides that, I've tried all the combinations that are possible, and one of them should be the correct answer anyway. Is there a problem with my rotation calculation code, or is it something to do with the animation/transform methods?

Thanks,

Can.

Can Poyrazoğlu
  • 33,241
  • 48
  • 191
  • 389

1 Answers1

1

Your functions should probably be:

#define rotation_reflected(ANG) CGAffineTransformMakeRotation(M_PI/2 + (ANG * M_PI / 180.0))
#define rotation(ANG) CGAffineTransformMakeRotation(-M_PI/2 - (ANG * M_PI / 180.0))

Note the + sign in the first line; you want the two objects to rotate in the opposite directions. You're still not going to have the right appearance, though, unless you flip one of your views (mirroring can't be simulated by rotations alone). Try making a subview which has a scale transform of -1 in, say, the y axis.

This might not do what you want, though, because there's no way for the transform to know which direction you're trying to rotate in. (Imagine you were rotating from noon to 6 o'clock; you'd specify from up to down, but the CABasicAnimation doesn't know if you mean clockwise or counter-clockwise; there's no "sign" to a transform, so it can't tell 180 degrees from -180 degrees.)

The way to get the desired effect is to use CAValueFunction. Rather than specifying the from and to transforms, you specify what you want to do (rotate around the Z axis) and from what angle you want to rotate from and to (in this case, it will respect the sign). To quote the CAValueFunction docs:

You use a value transform function that rotates from 0° to 180° around the z-axis by creating a CAValueTransform function specifying the kCAValueFunctionRotateZ and then creating an animation with a fromValue of 0, a toValue of M_PI, and set the animation’s valueTransform property to the value transform instance.

So, you'd want something like:

-(void)startWanderingIndicator{
    CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform"];
    anim.valueFunction = [CAValueFunction functionWithName:kCAValueFunctionRotateZ];
    anim.toValue = rotation(180);
    anim.duration = 4.0f;
    anim.fillMode = kCAFillModeForwards;
    anim.removedOnCompletion = NO;
    [self.indicatorView.layer addAnimation:anim forKey:@"rotation"];


    anim = [CABasicAnimation animationWithKeyPath:@"transform"];
    anim.valueFunction = [CAValueFunction functionWithName:kCAValueFunctionRotateZ];
    anim.toValue = rotation_reflected(180);
    anim.duration = 4.0f;
    anim.fillMode = kCAFillModeForwards;
    anim.removedOnCompletion = NO;
    [self.indicatorReflectionView.layer addAnimation:anim forKey:@"rotation"];
}
Jesse Rusak
  • 56,530
  • 12
  • 101
  • 102
  • i've tried your code, but this time, the indicator goes from starting position to 45 degrees, then comes back to its initial position, with an smooeth, easing animation. i can't make it to work. i've also tried setting -180 degrees to force going the other way around, but then it doesn't move entirely. don't really know what's going on – Can Poyrazoğlu Jan 15 '13 at 13:01
  • i've ended up implementing successfully with the following approach: http://stackoverflow.com/questions/1149635/how-can-i-enforce-an-specific-direction-i-e-clockwise-of-rotation-in-core-ani using keyframes with cakeyframeanimation and keyframe values and keytimes. i've set the value for the 0.5time of the keytime of the animation, and it animated correctly – Can Poyrazoğlu Jan 15 '13 at 13:17
  • @Jesse, I found your post when researching CAValueTransform, and the valueTransform property of CAAnimation. The only mention I can find of the valueTransform property is the quote you posted in the CAValueTransform class reference. Where is the valueTransform property of CAAnimation documented? I can't find it anywhere. Is it in one of the ancestor classes? In one of the protocols CAAnimation conforms to? ::Puzzled:: – Duncan C Jan 25 '14 at 17:12
  • @DuncanC I think it's a typo in the docs; they mean `valueFunction`, not `valueTransform`. – Jesse Rusak Jan 27 '14 at 13:42
  • @JesseRusak, Yup, there does appear to be a typo in the docs. I just posted a "provide feedback" report on it. Would you mind doing the same? I wasted a couple of hours searching in vain for "valueTransform", which doesn't exist. Grr... – Duncan C Jan 27 '14 at 14:07