Can one customize the underside color of curling view in a UIViewAnimationTransitionCurlUp animation. The default seems to be gray/white but i needed a different one.
Officially, no.
Unofficially, you can change the color using undocumented methods – but it will cause your app to be rejected if you target for AppStore,
CAFilter* trans_filter = [CAFilter filterWithName:#"pageCurl"];
[trans_filter setValue:[NSArray arrayWithObjects:
[NSNumber numberWithFloat:1.0f], // Red
[NSNumber numberWithFloat:0.0f], // Green
[NSNumber numberWithFloat:0.0f], // Blue
[NSNumber numberWithFloat:1.0f], // Alpha
nil] forKey:#"inputColor"];
CATransition* trans = [CATransition animation];
trans.filter = trans_filter;
trans.duration = 2; // etc.
[the_superview_containing_the_transitions.layer addAnimation:trans
forKey:#"transition"];
[the_old_view removeFromSuperview];
[the_superview_containing_the_transitions addSubview:the_new_view];
Related
Please could you tell me how to rotate a UIView (with a pie chart on) as if its on a pivot. It will only rotate once the user has flicked the view.
You'd use something like this:
- (void)spin
{
CABasicAnimation *fullRotation;
fullRotation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
fullRotation.fromValue = [NSNumber numberWithFloat:0];
fullRotation.toValue = [NSNumber numberWithFloat:M_PI * 360 / 180.0];
fullRotation.duration = 0.25;
fullRotation.repeatCount = 1;
[self.view.layer addAnimation:fullRotation forKey:#"360"];
}
Merely call the spin method when you want to spin 360º. Adjust as needed to spin more, less, faster, slower, etc.
EDIT: Should note, that in the above code, the view property is the one we're rotating, in case it wasn't obvious. So change self.view to whatever view you want to rotate.
I'm trying to implement a flip animation to be used in board game like iPhone-application. The animation is supposed to look like a game piece that rotates and changes to the color of its back (kind of like an Reversi piece). I've managed to create an animation that flips the piece around its orthogonal axis, but when I try to flip it around a diagonal axis by changing the rotation around the z-axis the actual image also gets rotated (not surprisingly). Instead I would like to rotate the image "as is" around a diagonal axis.
I have tried to change layer.sublayerTransform but with no success.
Here is my current implementation. It works by doing a trick to resolve the issue of getting a mirrored image at the end of the animation. The solution is to not actually rotate the layer 180 degrees, instead it rotates it 90 degrees, changes image and then rotates it back.
Final version: Based on Lorenzos suggestion to create a discrete keyed animation and calculate the transformation matrix for each frame. This version instead tries to estimate number of "guiding" frames needed based on the layer size and then uses a linear keyed animation. This version rotates with a arbitrary angle so to rotate around diagonal line use a 45 degree angle.
Example usage:
[someclass flipLayer:layer image:image angle:M_PI/4]
Implementation:
- (void)animationDidStop:(CAAnimationGroup *)animation
finished:(BOOL)finished {
CALayer *layer = [animation valueForKey:#"layer"];
if([[animation valueForKey:#"name"] isEqual:#"fadeAnimation"]) {
/* code for another animation */
} else if([[animation valueForKey:#"name"] isEqual:#"flipAnimation"]) {
layer.contents = [animation valueForKey:#"image"];
}
[layer removeAllAnimations];
}
- (void)flipLayer:(CALayer *)layer
image:(CGImageRef)image
angle:(float)angle {
const float duration = 0.5f;
CAKeyframeAnimation *rotate = [CAKeyframeAnimation
animationWithKeyPath:#"transform"];
NSMutableArray *values = [[[NSMutableArray alloc] init] autorelease];
NSMutableArray *times = [[[NSMutableArray alloc] init] autorelease];
/* bigger layers need more "guiding" values */
int frames = MAX(layer.bounds.size.width, layer.bounds.size.height) / 2;
int i;
for (i = 0; i < frames; i++) {
/* create a scale value going from 1.0 to 0.1 to 1.0 */
float scale = MAX(fabs((float)(frames-i*2)/(frames - 1)), 0.1);
CGAffineTransform t1, t2, t3;
t1 = CGAffineTransformMakeRotation(angle);
t2 = CGAffineTransformScale(t1, scale, 1.0f);
t3 = CGAffineTransformRotate(t2, -angle);
CATransform3D trans = CATransform3DMakeAffineTransform(t3);
[values addObject:[NSValue valueWithCATransform3D:trans]];
[times addObject:[NSNumber numberWithFloat:(float)i/(frames - 1)]];
}
rotate.values = values;
rotate.keyTimes = times;
rotate.duration = duration;
rotate.calculationMode = kCAAnimationLinear;
CAKeyframeAnimation *replace = [CAKeyframeAnimation
animationWithKeyPath:#"contents"];
replace.duration = duration / 2;
replace.beginTime = duration / 2;
replace.values = [NSArray arrayWithObjects:(id)image, nil];
replace.keyTimes = [NSArray arrayWithObjects:
[NSNumber numberWithDouble:0.0f], nil];
replace.calculationMode = kCAAnimationDiscrete;
CAAnimationGroup *group = [CAAnimationGroup animation];
group.duration = duration;
group.timingFunction = [CAMediaTimingFunction
functionWithName:kCAMediaTimingFunctionLinear];
group.animations = [NSArray arrayWithObjects:rotate, replace, nil];
group.delegate = self;
group.removedOnCompletion = NO;
group.fillMode = kCAFillModeForwards;
[group setValue:#"flipAnimation" forKey:#"name"];
[group setValue:layer forKey:#"layer"];
[group setValue:(id)image forKey:#"image"];
[layer addAnimation:group forKey:nil];
}
Original code:
+ (void)flipLayer:(CALayer *)layer
toImage:(CGImageRef)image
withAngle:(double)angle {
const float duration = 0.5f;
CAKeyframeAnimation *diag = [CAKeyframeAnimation
animationWithKeyPath:#"transform.rotation.z"];
diag.duration = duration;
diag.values = [NSArray arrayWithObjects:
[NSNumber numberWithDouble:angle],
[NSNumber numberWithDouble:0.0f],
nil];
diag.keyTimes = [NSArray arrayWithObjects:
[NSNumber numberWithDouble:0.0f],
[NSNumber numberWithDouble:1.0f],
nil];
diag.calculationMode = kCAAnimationDiscrete;
CAKeyframeAnimation *flip = [CAKeyframeAnimation
animationWithKeyPath:#"transform.rotation.y"];
flip.duration = duration;
flip.values = [NSArray arrayWithObjects:
[NSNumber numberWithDouble:0.0f],
[NSNumber numberWithDouble:M_PI / 2],
[NSNumber numberWithDouble:0.0f],
nil];
flip.keyTimes = [NSArray arrayWithObjects:
[NSNumber numberWithDouble:0.0f],
[NSNumber numberWithDouble:0.5f],
[NSNumber numberWithDouble:1.0f],
nil];
flip.calculationMode = kCAAnimationLinear;
CAKeyframeAnimation *replace = [CAKeyframeAnimation
animationWithKeyPath:#"contents"];
replace.duration = duration / 2;
replace.beginTime = duration / 2;
replace.values = [NSArray arrayWithObjects:(id)image, nil];
replace.keyTimes = [NSArray arrayWithObjects:
[NSNumber numberWithDouble:0.0f], nil];
replace.calculationMode = kCAAnimationDiscrete;
CAAnimationGroup *group = [CAAnimationGroup animation];
group.removedOnCompletion = NO;
group.duration = duration;
group.timingFunction = [CAMediaTimingFunction
functionWithName:kCAMediaTimingFunctionLinear];
group.animations = [NSArray arrayWithObjects:diag, flip, replace, nil];
group.fillMode = kCAFillModeForwards;
[layer addAnimation:group forKey:nil];
}
you can fake it this way: create an affine transform that collapse the layer along it's diagonal:
A-----B B
| | /
| | -> A&D
| | /
C-----D C
change the image, and trasform the CALayer back in another animation.
This will create the illusion of the layer rotating around its diagonal.
the matrix for that should be if I remember math correctly:
0.5 0.5 0
0.5 0.5 0
0 0 1
Update:
ok, CA doen't really likes to use degenerate transforms, but you can approximate it this way:
CGAffineTransform t1 = CGAffineTransformMakeRotation(M_PI/4.0f);
CGAffineTransform t2 = CGAffineTransformScale(t1, 0.001f, 1.0f);
CGAffineTransform t3 = CGAffineTransformRotate(t2,-M_PI/4.0f);
in my tests on the simulator there still was a problem because the rotations happens faster than te translation so with a solid black square the effect was a bit weird. I suppose that if you have a centered sprite with transparent area around it the effect will be close to what expected. You can then tweak the value of the t3 matrix to see if you get a more appealing result.
after more research, it appears that one should animate it's own transition via keyframes to obtaim the maximum control of the transition itself. say you were to display this animation in a second, you should make ten matrix to be shown at each tenth of a second withouot interpolation using kCAAnimationDiscrete; those matrix can be generated via the code below:
CGAffineTransform t1 = CGAffineTransformMakeRotation(M_PI/4.0f);
CGAffineTransform t2 = CGAffineTransformScale(t1, animationStepValue, 1.0f);
CGAffineTransform t3 = CGAffineTransformRotate(t2,-M_PI/4.0f);
where animationStepValue for ech of the keyFrame is taken from this progression:
{1 0.7 0.5 0.3 0.1 0.3 0.5 0.7 1}
that is: you're generating ten different transformation matrix (actually 9), pushing them as keyframes to be shown at each tenth of a second, and then using the "don't interpolate" parameter. you can tweak the animation number for balancing smoothness and performance*
*sorry for possible errors, this last part was written without a spellchecker.
I got it solved. You probably already have a solution as well, but here is what I have found. It is quite simple really...
You can use a CABasicAnimation to do the diagonal rotation, but it needs to be the concatenation of two matrices, namely the existing matrix of the layer, plus a CATransform3DRotate. The "trick" is, in the 3DRotate you need to specify the coordinates to rotate around.
The code looks something like this:
CATransform3DConcat(theLayer.transform, CATransform3DRotate(CATransform3DIdentity, M_PI/2, -1, 1, 0));
This will make a rotation that appears as though the upper-left corner of the square is rotating around the axis Y=X, and travelling to the lower-right corner.
The code to animate looks like this:
CABasicAnimation *ani1 = [CABasicAnimation animationWithKeyPath:#"transform"];
// set self as the delegate so you can implement (void)animationDidStop:finished: to handle anything you might want to do upon completion
[ani1 setDelegate:self];
// set the duration of the animation - a float
[ani1 setDuration:dur];
// set the animation's "toValue" which MUST be wrapped in an NSValue instance (except special cases such as colors)
ani1.toValue = [NSValue valueWithCATransform3D:CATransform3DConcat(theLayer.transform, CATransform3DRotate(CATransform3DIdentity, M_PI/2, -1, 1, 0))];
// give the animation a name so you can check it in the animationDidStop:finished: method
[ani1 setValue:#"shrink" forKey:#"name"];
// finally, apply the animation
[theLayer addAnimation:ani1 forKey#"arbitraryKey"];
That's it! That code will rotate the square (theLayer) to invisibility as it travels 90-degrees and presents itself orthogonally to the screen. You can then change the color, and do the exact same animation to bring it back around. The same animation works because we are concatenating the matrices, so each time you want to rotate, just do this twice, or change M_PI/2 to M_PI.
Lastly, it should be noted, and this drove me nuts, that upon completion, the layer will snap back to its original state unless you explicitly set it to the end-animation state. This means, just before the line [theLayer addAnimation:ani1 forKey#"arbitraryKey"]; you will want to add
theLayer.transform = CATransform3DConcat(v.theSquare.transform, CATransform3DRotate(CATransform3DIdentity, M_PI/2, -1, 1, 0));
to set its value for after the animation completes. This will prevent the snapping back to original state.
Hope this helps. If not you then perhaps someone else who was banging their head against the wall like we were! :)
Cheers,
Chris
Here is a Xamarin iOS example I use to flap the corner of a square button, like a dog ear (easily ported to obj-c):
Method 1: use a rotation animation with 1 for both x and y axes (examples in Xamarin.iOS, but easily portable to obj-c):
// add to the UIView subclass you wish to rotate, where necessary
AnimateNotify(0.10, 0, UIViewAnimationOptions.CurveEaseOut | UIViewAnimationOptions.AllowUserInteraction | UIViewAnimationOptions.BeginFromCurrentState, () =>
{
// note the final 3 params indicate "rotate around x&y axes, but not z"
var transf = CATransform3D.MakeRotation(-1 * (nfloat)Math.PI / 4, 1, 1, 0);
transf.m34 = 1.0f / -500;
Layer.Transform = transf;
}, null);
Method 2: just add an x-axis rotation, and y-axis rotation to a CAAnimationGroup so they run at the same time:
// add to the UIView subclass you wish to rotate, where necessary
AnimateNotify(1.0, 0, UIViewAnimationOptions.CurveEaseOut | UIViewAnimationOptions.AllowUserInteraction | UIViewAnimationOptions.BeginFromCurrentState, () =>
{
nfloat angleTo = -1 * (nfloat)Math.PI / 4;
nfloat angleFrom = 0.0f ;
string animKey = "rotate";
// y-axis rotation
var anim = new CABasicAnimation();
anim.KeyPath = "transform.rotation.y";
anim.AutoReverses = false;
anim.Duration = 0.1f;
anim.From = new NSNumber(angleFrom);
anim.To = new NSNumber(angleTo);
// x-axis rotation
var animX = new CABasicAnimation();
animX.KeyPath = "transform.rotation.x";
animX.AutoReverses = false;
animX.Duration = 0.1f;
animX.From = new NSNumber(angleFrom);
animX.To = new NSNumber(angleTo);
// add both rotations to a group, to run simultaneously
var animGroup = new CAAnimationGroup();
animGroup.Duration = 0.1f;
animGroup.AutoReverses = false;
animGroup.Animations = new CAAnimation[] {anim, animX};
Layer.AddAnimation(animGroup, animKey);
// add perspective
var transf = CATransform3D.Identity;
transf.m34 = 1.0f / 500;
Layer.Transform = transf;
}, null);
I'm developing a game in which I want my image to reduce in size gradually. I'm reducing the frame size gradually in my code when it works fine. [I've already used CGAffineTransform and it doesn't suit my requirement.]
-(void)function
{
ravanImage1.frame=CGRectMake(150,((ravanImage1.frame.origin.y)-5),q,z);
if(ravanImage1.center.y>=300&&ravanImage1.center.y<=370)
{
q=60;
z=60;
ravanImage1.frame=CGRectMake(150,((ravanImage1.frame.origin.y)-5),q,z);
}
if(ravanImage1.center.y>=230&&ravanImage1.center.y<=299)
{
q=40;
z=40;
ravanImage1.frame=CGRectMake(150,((ravanImage1.frame.origin.y)-5),q,z);
}
if(ravanImage1.center.y>=150&&ravanImage1.center.y<=229)
{
q=20;
z=20;
ravanImage1.frame=CGRectMake(150,((ravanImage1.frame.origin.y)-5),q,z);
}
}
But when I apply a while loop for the same code specifying wheather at what point to stop reducing the frame("while that point isn't reached"), it doesn't show the image frame reduction little by little as it shows it otherwise, but directly places the image at the end point with proper frame.
I want it to get displyed the way it gets without the while loop i.e. reduction little by little. Yes, while debugging it steps through all the steps properly.
Can anybody please help me?
As others have pointed out, manually adjusting the frame of your view will give you terrible performance. If you really don't want to use a standard UIView animation block for changing your view, you can specify bounds size values to animate through using a CAKeyframeAnimation applied to your view's layer:
CAKeyframeAnimation * customSizeAnimation = [CAKeyframeAnimation animationWithKeyPath:#"bounds.size"];
NSArray *sizeValues = [NSArray arrayWithObjects:[NSValue valueWithCGSize:size1], [NSValue valueWithCGSize:size2], [NSValue valueWithCGSize:size3], nil];
[customSizeAnimation setValues:frameValues];
NSArray *times = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0f], [NSNumber numberWithFloat:0.5f], [NSNumber numberWithFloat:1.0f], nil];
[customSizeAnimation setKeyTimes:times];
customSizeAnimation.fillMode = kCAFillModeForwards;
customSizeAnimation.removedOnCompletion = NO;
[view.layer addAnimation: customSizeAnimation forKey:#"customSizeAnimation"];
This animation will start at size1, pass through size2 at the midway point in the animation, and end at size3. You can have an arbitrary number of key frames and times for your animation, so you should be able to achieve the effect you desire
EDIT (1/5/2010): Removed kCAAnimationPaced as a calculationMode, which would cause the key times to be ignored. Also, I forgot that frame was a derived property, so you need to animate something like the bounds size instead.
The reason it does that is because it executes the while loop very quickly. I think the best thing to do is put some sort of a delay timer after each step of the while loop, then you'll see each step and it won't just 'jump' to it's final state.
[self setTimer: [NSTimer scheduledTimerWithTimeInterval: 3.5
target: self
selector: #selector (function_name)
userInfo: nil
repeats: YES]];
try using this.
This' my move function in which I'm trying to change the size of my imageView. If you can point out any error, I'll be really grateful..
-(void)move
{
UIImageView *imageViewForAnimation = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"ravan.jpg"]];
imageViewForAnimation.alpha = 1.0f;
CGSize size1=CGSizeMake(60,60);
CGSize size2=CGSizeMake(40,40);
CGSize size3=CGSizeMake(20,20);
CAKeyframeAnimation *customFrameAnimation = [CAKeyframeAnimation animationWithKeyPath:#"frame"];
NSArray *sizeValues = [NSArray arrayWithObjects:[NSValue valueWithCGSize:size1], [NSValue valueWithCGSize:size2], [NSValue valueWithCGSize:size3], nil];
[customFrameAnimation setValues:sizeValues];
customFrameAnimation.duration=10.0;
NSArray *times = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0f], [NSNumber numberWithFloat:0.5f], [NSNumber numberWithFloat:1.0f], nil];
[customFrameAnimation setKeyTimes:times];
customFrameAnimation.fillMode = kCAFillModeForwards;
customFrameAnimation.removedOnCompletion = NO;
[imageViewForAnimation.layer addAnimation:customFrameAnimation forKey:#"customFrameAnimation"];
[self.view addSubview:imageViewForAnimation];
}
I am trying to create a falling coin. The coin image is a CALayer with 2 CABasicAnimations on it - a falling down and a rotation one. When the falling down animation gets to its end, it stays there. The rotation animation though, which is supposed to be random and end up in a different angle every time, just pops back to the original CALAyer image.
I want it to stay in the angle it finished the animation on. Is it possible? How do I do it?
Code:
//Moving down animation:
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"transform.translation.y"];
anim.duration = 1;
anim.autoreverses = NO;
anim.removedOnCompletion = YES;
anim.fromValue = [NSNumber numberWithInt: -80 - row_height * (8 - _col)];
anim.toValue = [NSNumber numberWithInt: 0];
//Rotation Animation:
CABasicAnimation *rota = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
rota.duration = 4;
rota.autoreverses = NO;
rota.removedOnCompletion = NO;
rota.fromValue = [NSNumber numberWithFloat: 0];
rota.toValue = [NSNumber numberWithFloat: 2.5 * 3.15 ];
[cl addAnimation: rota forKey: #"rotation"];
[cl addAnimation: anim forKey: #"animateFalling"];
Have you set the removedOnCompletion property of the rotation animation to NO, e.g.,
rota.removedOnCompletion = NO;
That should leave the presentation layer where it was when the animation finished. The default is YES, which will snap back to the model value, i.e., the behavior you describe.
The fillMode should also be set, i.e.,
rota.fillMode = kCAFillModeForwards;
I was trying to rotate an arrow back and forth, like the Twitter/Facebook "Pull to Refresh" effect.
The problem is, I was doing the rotation back and forth on the same UIView, so after adding
rotation.removedOnCompletion = NO;
rotation.fillMode = kCAFillModeForwards;
The forward animation war working OK but the backwards animation was not working at all.
So I added the last line suggested by yeahdixon, and in addition set the view's transform to the animation's completed state: (rotation by 180 degrees)
[myview.layer removeAllAnimations];
myView.transform = CGAffineTransformMakeRotation(M_PI);
For the 'restore' animation (backwards) I do this on completion:
myView.transform = CGAffineTransformMakeRotation(0);
...and it works fine. Somehow it doesn't need the removeAllAnimations call.
I found that by setting : removedOnCompletion = NO;
did not produce a visible leak in instruments, but did not get deallocated and was accumulating a small amount of memory. IDK if its my implementation or what, but by adding removeAllAnimations at the end of the animation seemed to clear out this tiny bit of residual memory.
[myview.layer removeAllAnimations];
Got a question. I have a subclassed UIView that is acting as my background where I am scrolling the ground. The code is working really nice and according to the Instrumentation, I am not leaking nor is my created and still living Object allocation growing.
I have discovered else where in my application that adding an animation to a UIImageView that is owned by my subclassed UIView seems to bump up my retain count and removing all animations when I am done drops it back down.
My question is this, when you add an animation to a layer with a key, I am assuming that if there is already a used animation in that entry position in the backing dictionary that it is released and goes into the autorelease pool?
For example:
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
NSString *keyValue = [theAnimation valueForKey:#"name"];
if ( [keyValue isEqual:#"step1"] && flag ) {
groundImageView2.layer.position = endPos;
CABasicAnimation *position = [CABasicAnimation animationWithKeyPath:#"position"];
position.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
position.toValue = [NSValue valueWithCGPoint:midEndPos];
position.duration = (kGroundSpeed/3.8);
position.fillMode = kCAFillModeForwards;
[position setDelegate:self];
[position setRemovedOnCompletion:NO];
[position setValue:#"step2-1" forKey:#"name"];
[groundImageView2.layer addAnimation:position forKey:#"positionAnimation"];
groundImageView1.layer.position = startPos;
position = [CABasicAnimation animationWithKeyPath:#"position"];
position.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
position.toValue = [NSValue valueWithCGPoint:midStartPos];
position.duration = (kGroundSpeed/3.8);
position.fillMode = kCAFillModeForwards;
[position setDelegate:self];
[position setRemovedOnCompletion:NO];
[position setValue:#"step2-2" forKey:#"name"];
[groundImageView1.layer addAnimation:position forKey:#"positionAnimation"];
}
else if ( [keyValue isEqual:#"step2-2"] && flag ) {
groundImageView1.layer.position = midStartPos;
CABasicAnimation *position = [CABasicAnimation animationWithKeyPath:#"position"];
position.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
position.toValue = [NSValue valueWithCGPoint:endPos];
position.duration = 12;
position.fillMode = kCAFillModeForwards;
[position setDelegate:self];
[position setRemovedOnCompletion:NO];
[position setValue:#"step1" forKey:#"name"];
[groundImageView1.layer addAnimation:position forKey:#"positionAnimation"];
}
}
This chains animations infinitely, and as I said one it is running the created and living object allocation doesn't change. I am assuming everytime I add an animation the one that exists in that key position is released.
Just wondering I am correct. Also, I am relatively new to Core Animation. I tried to play around with re-using the animations but got a little impatient. Is it possible to reuse animations?
Thanks!
Bryan
Your assumption is difficult to verify without Apple's source code. When you call addAnimation:forKey:, the CALayer makes a copy of the animation object. There is no guarantee from the API (in the docs or elsewhere) that it will release a running animation when another is added for the same key. It would be much safer for you to explicitly call removeAnimation:forKey: rather than relying on the API to do what you want.
Also, why have you set removedOnCompletion to NO in the code above? The default behavior of a CAAnimation is to remove itself from the layer once it has completed, and then call animationDidStop:finished: on the delegate. You can then add any other animation to the layer with the same key. Isn't this exactly what you're trying to do? It seems like you might be over thinking it a little.
As for your question about reusing animations: since the animation object is copied when added to a CALayer, you can hold a reference to an animation object that you create and keep adding it to as many layers as you want as many times as you like.