Two property changes in the same animation block have different durations. Why? - iphone

In the following UIView animation block, I do a CGAffineTransformMakeScale and a CGAffineTransformMakeRotation, and though the duration is set to 1.0f, the scale goes really fast and the rotation goes in 1 second like it should.
It must be my lack of understanding about how AffineTransforms are applied but I can't figure it out.
What gives?
EDIT: Vladimir's answer worked great. I was changing the same property twice, rather than changing two properties. To make two changes to a transform property, you have to make a transform with the initial change, then add the second change to that transform and then set you object's transform from there. This way, you can chain as many together as you want.
CGColorRef color = [[colorArray objectAtIndex:colorIndex] CGColor];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:1.0f];
[[self layer] setFillMode:kCAFillModeForwards];
[self setTransform:CGAffineTransformMakeScale(2.0, 2.0)];
[self setTransform:CGAffineTransformMakeRotation(M_PI / 4)];
[[self layer] setBackgroundColor:color];
[UIView commitAnimations];

[self setTransform:CGAffineTransformMakeScale(2.0, 2.0)];
[self setTransform:CGAffineTransformMakeRotation(M_PI / 4)];
2nd line overrides the effect of 1st line. You should construct the whole transform and then apply it to your view:
CGAffineTransform tr = CGAffineTransformMakeScale(2.0f,2.0f);
tr = CGAffineTransformRotate(tr, M_PI/4);
[self setTransform: tr];

Related

Oscillating UIView With Stopping Motion (iOS)

I need a view to enter the screen with oscillating animation and finally, the animation should stop in a natural way (decreasing oscillations - pendulum effect). I have added the subview above the screen so that the view rotates into the screen when required. The code for adding the subview is:
myView.layer.anchorPoint = CGPointMake(1.0, 0.0);
[[self view] addSubview:myView];
[myView setHidden:YES];
// Rotate 75 degrees to hide it off screen
CGAffineTransform rotationTransform = CGAffineTransformIdentity;
rotationTransform = CGAffineTransformRotate(rotationTransform, DEGREES_RADIANS(75));
bannerView.transform = rotationTransform;
bannerView.center = CGPointMake(((self.view.bounds.size.width)/2.0), -5.0);
[self performSelector:#selector(animateSwing) withObject:nil afterDelay:3.0];
The way I'm trying to achieve this that the view should rotate one full semi circle & back rotation, then rotate one semi circle rotation and finally come to halt at desired point using EaseOut animation curve. The code for my animateSwing() method is given below:
- (void)animateSwing {
NSLog(#"ANIMATING");
[myView setHidden:NO];
CGAffineTransform swingTransform = CGAffineTransformIdentity;
swingTransform = CGAffineTransformRotate(swingTransform, DEGREES_RADIANS(-20));
[UIView animateWithDuration:0.30
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
[UIView setAnimationRepeatCount:1.5];
[UIView setAnimationRepeatAutoreverses:YES];
myView.transform = swingTransform;
}completion:^(BOOL finished){
[UIView animateWithDuration:0.10
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
myView.transform = CGAffineTransformMakeRotation(DEGREES_RADIANS(0));
}completion:^(BOOL Finished){
}];
}];
}
For some reason the above code isn't working. If I do not chain animations, the code performs the semi-circle routine. But if I chain animations like above, it just oscillates a little bit around the desired point and ends abruptly.
Please suggest a fix to this code OR suggest a way to implement the required animation
Thanks
You want to use a keyframe animation. I actually have an example of a "decreasing waggle" animation in my book (http://www.apeth.com/iOSBook/ch17.html#_keyframe_animation):
CompassLayer* c = (CompassLayer*)self.compass.layer;
NSMutableArray* values = [NSMutableArray array];
[values addObject: #0.0f];
int direction = 1;
for (int i = 20; i < 60; i += 5, direction *= -1) { // alternate directions
[values addObject: #(direction*M_PI/(float)i)];
}
[values addObject: #0.0f];
CAKeyframeAnimation* anim =
[CAKeyframeAnimation animationWithKeyPath:#"transform"];
anim.values = values;
anim.additive = YES;
anim.valueFunction =
[CAValueFunction functionWithName: kCAValueFunctionRotateZ];
[c.arrow addAnimation:anim forKey:nil];
Of course that isn't identical to what you're trying to do, but it should get you started.

How to scale and rotate a view in a single animation

I am trying to present a view by making it emerge from the centre of the screen while growing to its full size, while also rotating it around the x-axis in a 3D manner. When I create the view I apply a transform to it to make sure it is shrunk and rotated to start off with (it is so small it is not actually visible), then I try to use a CATransform3D like this:
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"transform"];
CATransform3D transform = CATransform3DRotate(view.layer.transform, M_PI, 1.0, 0, 0);
transform = CATransform3DScale(transform, 1000, 1000, 1000);
transform.m34 = 1.0 / 10000;
[anim setToValue:[NSValue valueWithCATransform3D:transform]];
[anim setDuration:0.75f];
anim.removedOnCompletion = NO;
anim.delegate = self;
[view.layer addAnimation:anim forKey:#"grow"];
However this animation does not change the actual transform of the layer, so I also do this:
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
view.layer.transform = CATransform3DIdentity;
[view.layer removeAllAnimations];
}
to set the transform once the animation stops. However this sometimes results in a noticeable flicker at the end of the animation. I believe this is because the animation puts the original transform back at the end of the animation phase and this happens momentarily before the animationDidStop routine gets called. Is there a better way to do this?
Edit: Incorporating it into a UIView animation works as this way you can set the transform directly:
view.layer.transform = CATransform3DScale(CATransform3DIdentity, 0.001, 0.001, 0.001);
view.layer.transform = CATransform3DRotate(view.layer.transform, M_PI, 1.0, 0.0, 0.0);
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.75];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
CATransform3D transform = CATransform3DRotate(view.layer.transform, M_PI, 1.0, 0, 0);
transform = CATransform3DScale(rotationTransform, 1000, 1000, 1000);
transform.m34 = 1.0 / -500;
view.layer.transform = transform;
[UIView commitAnimations];
However, I would still like an answer to my original query, as to how to achieve the same successully using a CAAnimation, as that provides more flexibility for animations generally.
Edit2: It seems the answer to my original question (how to fix the problem with the CAAnimation) was actually very straightforward. To keep the end state (and remove the flicker), I just needed to add the following line:
anim.fillMode = kCAFillModeForwards;
This might help you:
animationView.layer.anchorPoint = CGPointMake(0.0f, 0.0f);
animationView.center = CGPointMake(0,
(animationView.center.y -
(animationView.bounds.size.height/2.0f)));
// start the Page Open
[UIView beginAnimations:#"Animation" context:nil];
[UIView setAnimationDuration:2.0];
// set angle as per requirement
[animationView.layer setValue:[NSNumber numberWithInt:180]
forKeyPath:#"transform.rotation.x"];
[UIView commitAnimations];
I know this is not a clean and short answer you were probably hoping for :)
But here (UIView Animation Tutorial: Practical Recipes
) you can find a downloadable example which also shows how to do scale & rotate.
You can see the scale and rotate if you run the example, click HUD and then click stop.
See this thread about how iPad app store does the rotate and scale for an answer and code : How can I flip and enlarge a UIView it at the same time like iOS 7 iPad App Store?

frame animation of uiimageView reduced

hie everyone, here is my code:
- (void)viewDidLoad {
[[UIAccelerometer sharedAccelerometer] setUpdateInterval:1.0/60.0];
[[UIAccelerometer sharedAccelerometer] setDelegate:self];
[super viewDidLoad];
}
-(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
valueX = acceleration.x *20.0;
valueY = acceleration.y *-20.0;
int newX = (int)(imageView.center.x+valueX);
int newY = (int)(imageView.center.y+valueY);
CGPoint newCenter = CGPointMake(newX,newY);
imageView.center=newCenter;
UIImageView* imageView2 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"ball.png"]];
imageView2.frame = CGRectMake(newX-12, newY-12, 24, 24);
[self.view addSubview:imageView2];
[UIView beginAnimations:nil context:imageView2];
[UIView setAnimationDuration:10.5];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
imageView2.frame = CGRectMake(newX-2, newY-2, 4, 4);
[imageView2 setAlpha:0.0];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(removeSmoke:finished:context:)];
[UIView commitAnimations];
if(imageView2.alpha=0){
[imageView2 removeFromSuperview];
}
[self.view bringSubviewToFront:imageView];
}
At first my animation is very smooth with 1.0/60.0 but after some seconds the animation bug and is not smooth, i think it become 1.0/5.0. I don't know why . How can I solve this please ? sorry for my english I'm french :/
I'm not completely clear on what you are trying to achieve. It seems as though every time the accelerometer fires, you add in a new imageView animation, and want to move it based on some rules, as well shrink the image down. There are problems with your code though. You never call [imageView2 release];, and so you are probably getting memory problems after awhile.
You may also be doing too much computation in the method #selector(removeSmoke:finished:context:), but I'd need to see code in order to help with that.
If you know how many UIImageViews you are planning on having at one time, it is probably best to pre-load them, and put them in an array for easy access. That way, you can move one UIImageView, and move to the next one.
If you are shooting for iOS 4.0 or higher, I'd also recommend using Block Animations. They are easier to use, and I find a lot easier on the processor as well. Something like:
[UIView animateWithDuration:10.5
animations:^{
CGRect newRect = imageView2.frame;
newRect.origin.x -= newX-2;
newRect.origin.y -= newY-2;
imageView2.frame = newRect;
}
completion:^(BOOL finished){
[UIView animateWithDuration:0.1
animations:^{
// This is where you complete your animation, maybe remove from view or something?
}
completion:^(BOOL finished){
;
}];
}];
Finally, instead of adding and removing from the view, I would suggest you just setting the imageView2.hidden = YES;, and that way it won't get drawn, and has the same effect. Hope that helps!

Fade background-color from one color to another in a UIView

How can I fade from one background-color to another color in a UIView?
In javascript (jQuery) it can be done by doing the following:
$('body').animate({ backgroundColor: "#ff00ff"});
Would it be easier if I converted the colors to HSV?
EDIT:
Tried to do it with CABasicAnimation but it flashes white for unknown reasons. :/
CABasicAnimation* fade = [CABasicAnimation animationWithKeyPath:#"backgroundColor"];
fade.fillMode = kCAFillModeForwards;
fade.removedOnCompletion = NO;
// fade.fromValue = (id)[self.view backgroundColor].CGColor;
fade.toValue = (id)[UIColor greenColor].CGColor;
[self.view.layer addAnimation:fade forKey:#"fade"];
Then I tried to find another way to do it and stumbled upon "implicit animation". The following works with a nice fade of a second:
[UIView beginAnimations:#"fade" context:nil];
[UIView setAnimationDuration:1];
[[self view] setBackgroundColor: [UIColor greenColor]];
[UIView commitAnimations];
Thanks, StackOverflow, for helping everyone learn even more. :)
You don't need to create two UIView and fade - why bother having an extra superfluous view hanging around when the color attribute itself is animatable?
You can use CAAnimation as shown above, but instead of adjusting the opacity of two views just adjust the backgroundColor of one.
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Animation_Types_Timing/Articles/PropertyAnimations.html

Core Animation problem on iPhone

I'm new to iPhone development, and doing some experimentation with Core Animation. I've run into a small problem regarding the duration of the animation I'm attempting.
Basically, Ive got a view with two subviews, and I'm trying to animate their opacity so that one fades in while the other fades out. Problem is, instead of a gradual fade in/out, the subviews simply switch instantly to/from full/zero opacity. I've tried to adjust the animation duration with CATransaction with no noticable effect. It's also not specific to animating opacity - animating position shows the same problem.
The code I'm using (inside a method of the superview) follows:
CALayer* oldLayer = ((UIView*) [[self subviews] objectAtIndex:0]).layer;
CALayer* newLayer = ((UIView*) [[self subviews] objectAtIndex:1]).layer;
[CATransaction begin];
[CATransaction setAnimationDuration:1.0f];
oldLayer.opacity = 0.0;
newLayer.opacity = 1.0;
[CATransaction commit];
Does anyone have an idea what the problem might be?
Noah's solution is the cleanest way to do what you want, but the reason why you're not seeing the animation is that implicit animations are disabled for CALayers that back UIViews. Because nothing is animating, the CATransaction is unable to set the duration for anything.
If you really wanted to perform this animation on the layer, you'd need to set up a manual CABasicAnimation to do this:
CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:#"opacity"];
opacityAnimation.removedOnCompletion = NO;
opacityAnimation.fillMode = kCAFillModeForwards;
[oldLayer addAnimation:opacityAnimation forKey:#"opacity"];
[newLayer addAnimation:opacityAnimation forKey:#"opacity"];
[CATransaction begin];
[CATransaction setAnimationDuration:1.0f];
oldLayer.opacity = 0.0;
newLayer.opacity = 1.0;
[CATransaction commit];
(Note that I'm not sure if you can re-use the opacity animation here. You might need to create separate instances for each layer.)
Is there a reason you're using Core Animation over the generic UIView animation wrapper? It's pretty straightforward to do something like this with
UIView *oldView = [[self subviews] objectAtIndex:0];
UIView *newView = [[self subviews] objectAtIndex:1];
[UIView beginAnimations:#"swapViews" context:nil];
[UIView setAnimationDuration:1];
oldView.alpha = 0;
newView.alpha = 1;
[UIView commitAnimations];
Do you need to use an explicit CATransaction here?
The docs here imply that you don't.