I have a car image in a UIImageView, with movement and rotation animations.
I have a tyre-mark image in a UIImageView, which i've added as a subview to the car.
This means that all the same movement and rotation animations apply to both.
What I want to do is leave a trail of tyre skidmarks.
Can anyone suggest a strategy on how to do this?
Searching through other topics I saw this snippet, not sure if I can use it:
UIGraphicsBeginImageContext(drawingView.bounds.size);
[drawingView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//then display viewImage in another UIImageView...
If it were usable, any clue on how to get it called during an animation?
That snippet is not quite what you're looking for. That snippet saves what is currently displayed in the context as a UIImage. You can use the ImageContext to draw as well, but not like that.
The ideal would be if CAKeyframeAnimation notified its delegate before doing the following keyframe; since I don't think that's possible (?) the only way I can think of doing something like this is using an array of positions, and using consecutive CABasicAnimation instances in order to do this. This is like a "poor man's" CAKeyframeAnimation.
Something like this:
- (void)viewDidLoad
{
[super viewDidLoad];
_step = 0;
_positions = [[NSArray alloc] initWithObjects:[NSValue valueWithCGPoint:CGPointMake(20.0, 20.0)],
[NSValue valueWithCGPoint:CGPointMake(40.0, 80.0)],
[NSValue valueWithCGPoint:CGPointMake(60.0, 120.0)],
[NSValue valueWithCGPoint:CGPointMake(80.0, 160.0)],
[NSValue valueWithCGPoint:CGPointMake(100.0, 200.0)],
[NSValue valueWithCGPoint:CGPointMake(120.0, 240.0)],
[NSValue valueWithCGPoint:CGPointMake(140.0, 280.0)],
[NSValue valueWithCGPoint:CGPointMake(160.0, 320.0)],
[NSValue valueWithCGPoint:CGPointMake(180.0, 360.0)],
[NSValue valueWithCGPoint:CGPointMake(200.0, 400.0)],
nil];
[self moveToNextPosition];
}
- (void)moveToNextPosition
{
if (_step < [_positions count] - 1)
{
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.fromValue = [_positions objectAtIndex:_step];
animation.toValue = [_positions objectAtIndex:(_step + 1)];
animation.delegate = self;
animation.removedOnCompletion = YES;
[_sprite.layer addAnimation:animation forKey:#"position"];
++_step;
}
else
{
_sprite.center = [[_positions objectAtIndex:_step] CGPointValue];
}
}
- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)finished
{
UIImageView *trail = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"sprite.png"]];
trail.center = [[_positions objectAtIndex:_step] CGPointValue];
[self.view insertSubview:trail belowSubview:_sprite];
[trail release];
[self moveToNextPosition];
}
In this case, the animations execute one after the other, with values specified in the _positions NSArray ivar, and the _step is incremented at every step. When each animation stops, we draw a sprite image below the one we're animating, and we restart our animation, until there are no more points to move to. And then we finish.
Hope this helps!
Related
In this code i am using animation.But at a time i need to change the image alpha value also.
-(void)animation
{
CGPoint point = CGPointMake(imgView.frame.origin.x, imgView.frame.origin.y);
imgView.layer.position = point;
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"position.x"];
anim.fromValue = [NSValue valueWithCGPoint:point];
anim.toValue = [NSValue valueWithCGPoint:CGPointMake(point.x + 50, point.y)];
anim.duration = 10.0f;
anim.repeatCount =1;
anim.removedOnCompletion = YES;
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[imgView.layer addAnimation:anim forKey:#"position.x"];
imgView.layer.position = point;
}
You can use the opacity key of CABasicAnimation for doing this:
CABasicAnimation *alphaAnimation = [CABasicAnimation animationWithKeyPath:#"opacity"];
alphaAnimation.fromValue = [NSNumber numberWithFloat:1.0];
alphaAnimation.toValue = [NSNumber numberWithFloat:0.0];
[imgView.layer addAnimation:alphaAnimation forKey:#"opacity"];
Edit:
For animating through specified values you can use CAKeyframeAnimation:
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"opacity"];
animation.duration = 10.0f;
animation.repeatCount = 1;
animation.values = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:1.0,
[NSNumber numberWithFloat:0.5],
[NSNumber numberWithFloat:1.0],nil];
animation.keyTimes = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:5.0],
[NSNumber numberWithFloat:10.0], nil];
[imgView.layer addAnimation:animation forKey:#"opacity"];
In order to decrease the alpha as the same time/rate the x position of your view is moving, just put the position setting code together with the alpha setting code in the same block of animation, like below:
CGPoint finalPoint = CGPointMake(500, imgView.center.y); //change for any final point you want
CGFloat finalAlpha = 0.0; //change the final alpha as you want
UIView animateWithDuration:1.0 animations:^{ //change the duration as you want
imgView.alpha = finalAlpha;
imgView.center = finalPoint;
}];
This "fades to invisible" self.view in 500ms.
Anything you do prior to invoking it will be set "immediately", everything you set within the `animation' block (like setting new frame coordinates) will be interpolated over the specified duration.
[UIView animateWithDuration:0.50
delay:0.0
options:( UIViewAnimationOptionAllowUserInteraction
| UIViewAnimationOptionBeginFromCurrentState
| UIViewAnimationOptionCurveEaseInOut)
animations:^ {
self.view.alpha = 0.0f ;
}
completion: ^(BOOL){
[self.view removeFromSuperview] ;
self.view = nil ;
}
] ;
You may use a CABasicAnimation object (a different one because you will animate a different KeyPath, namely alpha, for this) and use NSNumbers for fromValue and toValue.
Another (and easiest) way is to use the UIView helper methods dedicated to animation ( animateWithDuration:… and all), especially if you have to animate multiple properties at the same time (you may animate both the frame and the alpha with the same animation block if it fits your needs and simplify your code).
You can also mix CABasicAnimation for the frame and [UIView animateWithDuration:…] for the alpha, whatever combination you need depending on if your animations are complex and need to be customized (custom timing function, non-linear animation, …) or not.
//your code of changing x position will be here
now implement my code which is written below.
UIView animateWithDuration:1.0 animations:^{
imgView.alpha = 0.5;//must be in between 0 to 1
}];
now when your imageview move to next position as you are saying, write below code
//your code for imageview for next position..
now implement below code...
UIView animateWithDuration:0.5 animations:^{
imgView.alpha = 1.0;//must be in between 0 to 1
}];
let me know it is working or not!!!! Hope it helps...
Happy Coding!!!
I'm trying execute a couple of animations at the same time. One is transitioning from one uimageview to another and the other is animating translation.x of a label. The label resides on top of uiimageview.
But what I get is either translation working fine and transition happens immediately, or the transitioning -based on hidden property - also applies to my label which should only be shifted (it also goes from hidden to visible). I can't use caanimationgroup because they apply to different views.
//CAKeyFrameAnimation for sliding the label
...
CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:#"transform.translation.x"];
NSArray *xValues = #[[NSNumber numberWithFloat:myLabel.bounds.origin.x],
[NSNumber numberWithFloat:myLabel.bounds.origin.x + screenHalf],
[NSNumber numberWithFloat:myLabel.bounds.origin.x + screenHalf * 4]];
[anim setValues:xValues];
NSMutableArray *timeFrames = [NSMutableArray array];
CGFloat timeStep = 1.0 / ([xValues count] - 1);
for (NSInteger i = 0; i < [xValues count]; i++)
{
[timeFrames addObject:[NSNumber numberWithFloat:timeStep * i]];
}
[anim setKeyTimes:timeFrames];
[anim setDuration:duration];
[anim setFillMode:kCAFillModeForwards];
[anim setRemovedOnCompletion:FALSE];
[myLabel.layer addAnimation:anim forKey:nil];
...
//Transitioning from uiimageview to another one
...
CATransition *transition = [CATransition animation];
[transition setDuration:duration];
[transition setType:kCATransitionFade];
//These two are uiimageviews i'm switching from and to
initial.hidden = TRUE;
next.hidden = FALSE;
//Initial and next are subviews of container which itself is a subview of viewcontroller's main view
[container.layer addAnimation:transition forKey:#""];
If I call the animations like above the transition occurs immediately and label slides across screen correctly. If I change the last line to:
[self.view.layer addAnimation:transition forKey:#""];
Then hidden animation also applies to myLabel. What is the fix for combining animations like above, and also more elaborately what is the cause?
I would wrap the entire code in a CATransaction to group the different CAAnimations into a single group.
Psuedo-code for using this with CAKeyFrameAnimation would look like:
[CATransaction begin];
// set completion block if you want
[CATransaction setCompletionBlock:^{ NSLog(#"I'm done"); }];
// start a keyframe animation
CAKeyframeAnimation *key1 = .....
// start another keyframe animation block
CAKeyframeAnimation *key2 = ......
// Maybe do a basic animation
CABasicAnimation *anim1 = .....
// close out all the animations and have them start
[CATransaction commit];
I am messing around with a basic CAAnimation example where Im just trying to get a CALayer to rotate in a subclass of UIView. I start by making a new CALayer with a red background and inserting it into self.layer. I then make a CAAnimation that is supposed to be triggered on opacity changes and apply it to the CALayer, after which it spins as expected.
Weird thing is that if I try to apply the exact same animation using performSelector:withObject:afterDelay:, the CALayer no longer animates, although its opacity still changes. Even weirder is that if I use performSelector:withObject:afterDelay: to animate self.layer using pretty much the same code, it works.
Any ideas as to what's going on? Here is my code:
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
point = [[CAGradientLayer alloc] init];
point.backgroundColor = [UIColor redColor].CGColor;
point.bounds = CGRectMake(0.0f, 0.0f, 30.0f, 20.0f);
point.position = CGPointMake(100.0f, 100.0f);
[self.layer addSublayer:point];
[point release];
[self performSelector:#selector(test) withObject:nil afterDelay:2];
}
return self;
}
-(void)test {
[self spinLayer:point];
// [self spinLayer:self.layer]; works here
}
-(CAAnimation*)animationForSpinning {
CATransform3D transform;
transform = CATransform3DMakeRotation(M_PI/2.0, 0, 0, 1.0);
CABasicAnimation *basicAnimation = [CABasicAnimation animationWithKeyPath:#"transform"];
basicAnimation.toValue = [NSValue valueWithCATransform3D:transform];
basicAnimation.duration = 5;
basicAnimation.cumulative = NO;
basicAnimation.repeatCount = 10000;
return basicAnimation;
}
-(void)spinLayer:(CALayer*)layer {
CAAnimation *anim = [self animationForSpinning];
[layer addAnimation:anim forKey:#"opacity"];
layer.opacity = 0.6;
}
and this is my interface
#interface MyView : UIView {
CALayer *point;
}
-(void)test;
-(void)spinLayer:(CALayer*)layer;
-(CAAnimation*)animationForSpinning;
#end
Note: I am instantiating this view with a frame equal to [UIScreen mainScreen].applicationFrame from within the app delegate.
Change this:
[layer addAnimation:anim forKey:#"opacity"];
into this:
[layer addAnimation:anim
forKey:#"notOpacity!"];
What happens is that implicit animation layer.opacity = 0.6 overwrites you explicit animation. This happens because Core Animation uses property names when adding implicit animations.
(If you move layer.opacity = 0.6 above [layer addAnimation:anim forKey:#"opacity"]; it will also work, but layer.opacity will not get animated.)
And why it works without delay: because implicit animations are "enabled" only if layer is in the tree and been displayed.
I want to crossfade between two different UIImages but for a reason i cannot figure out my first UIImage stays when the second one fades in.
Here is the sourcecode of my animation (it does a lot more than just crossfading, but the crossfading is my only problem right now).
I need all of the three animations listed below to be executed at the same time.
CGMutablePathRef thePath = CGPathCreateMutable();
CGPathMoveToPoint(thePath,NULL,startPoint.x, startPoint.y);
CGPathAddLineToPoint(thePath, NULL, endPoint.x, endPoint.y);
CAKeyframeAnimation *positionAnimation =[CAKeyframeAnimation animationWithKeyPath:#"position"];
positionAnimation.path=thePath;
positionAnimation.duration=ti_duration;
positionAnimation.repeatCount=0;
positionAnimation.delegate = self;
positionAnimation.autoreverses=NO;
positionAnimation.fillMode=kCAFillModeForwards;
positionAnimation.removedOnCompletion = NO;
[self.layer addAnimation:positionAnimation forKey:s_direction];
CABasicAnimation *crossFade = [CABasicAnimation animationWithKeyPath:#"contents"];
crossFade.duration = ti_duration;
crossFade.fromValue = (id)img_startImage.CGImage;
crossFade.toValue = (id)img_transferImage.CGImage;
[self.iv_image.layer addAnimation:crossFade forKey:#"animateContentsToTransferState"];
[self.iv_image setImage:img_transferImage];
CAAnimationGroup *theGroup = [CAAnimationGroup animation];
theGroup.duration = ti_duration;
theGroup.repeatCount = 0;
theGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
theGroup.animations = [NSArray arrayWithObjects:/*positionAnimation,*/ crossFade, nil]; // you can add more
[self.layer addAnimation:theGroup forKey:#"move"];
[UIView beginAnimations:#"changeSize" context:nil];
[UIView setAnimationDuration:duration];
self.bounds = CGRectMake(self.bounds.origin.x, self.bounds.origin.y, self.transferSize.width, self.transferSize.height);
[UIView commitAnimations];
The animation method containing this sourcecode is in a subclass of UIView. This subclass contains several UIImages and one UIImageView where the image to be displayed is contained in.
Have i forgotten some essential thing or why is my first image not fading away?
Can it be because it is animated from some other animation at the same time?
I hope someone can help me with this.
Greets
Maverick
Think of CAAnimations as operations applied to the CALayer. The animations current state do not change the layers original contents.
When the original state seems to snap back at the end of the animation it is actually just the animations operation being removed, and the real layers state is being revealed again.
What you need to do is to register a delegate for your animation, and change the actual self.layer.contents when the animation ends.
First:
crossFade.delegate = self;
Then implement:
- (void)animationDidStop:(CABasicAnimation *)animation finished:(BOOL)flag {
self.layer.contents = animation.toValue;
}
Try this code...It worked for me.
-(void)crossFadeImage{
CABasicAnimation *crossFade = [CABasicAnimation animationWithKeyPath:#"contents"];
crossFade.duration = 2.0;
crossFade.fromValue = img1.CGImage;
crossFade.toValue = img2.CGImage;
[self.background.layer addAnimation:crossFade forKey:#"animateContents"];
self.background.image = img2;
}
I'm trying to slide down an image in an UIImageView, but I don't know which combination of UIContentMode and animation property is the right one to make that happen.
The image should always have the same size and should not be streched... all I want is, that nothing is visible first and then the frame extends and reveals the image.
Maybe it's easier if you see what I mean:
So, it sounds rather easy, but what UIContentMode should I use for the UIImageView and what property should I animate? Thank you!
I took your lead and made a screencast as well. Was this what you had in mind?
I put the animation repeating indefinitely so it would be easier to capture with a video, but it can be started at the press of a button, as well as frozen in place, showing the popover and its contents, until reversed to be hidden again.
I used Core Animation for that, instead of animating a UIView, since I wanted to use the mask property of CALayer to hide the popover and reveal it with a sliding animation.
Here is the code I used (same as in the video):
- (void)viewDidLoad
{
// Declaring the popover layer
CALayer *popover = [CALayer layer];
CGFloat popoverHeight = 64.0f;
CGFloat popoverWidth = 200.0f;
popover.frame = CGRectMake(50.0f, 100.0f, popoverWidth, popoverHeight);
popover.contents = (id) [UIImage imageNamed:#"popover.png"].CGImage;
// Declaring the mask layer
CALayer *maskLayer = [CALayer layer];
maskLayer.frame = CGRectMake(0, - popoverHeight, popoverWidth, popoverHeight);
maskLayer.backgroundColor = [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f].CGColor;
// Setting the animation (animates the mask layer)
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position.y"];
animation.byValue = [NSNumber numberWithFloat:popoverHeight];
animation.repeatCount = HUGE_VALF;
animation.duration = 2.0f;
[maskLayer addAnimation:animation forKey:#"position.y"];
//Assigning the animated maskLayer to the mask property of your popover
popover.mask = maskLayer;
[self.view.layer addSublayer:popover];
[super viewDidLoad];
}
NOTE: You have to import the QuartzCore framework into your project and write this line in your header file: #import <QuartzCore/QuartzCore.h>.
Tells if this works for you or if you need any more help setting this up.
Try this code.
Consider the UIImageView as imageView.
imageView.contentMode = UIViewContentModeScaleAspectFill;
CGRect imageRect = imageView.frame;
CGRect origImgRect = imageRect;
imageRect.size.height = 5;
imageView.frame = imageRect;
[UIView animateWithDuration:2.0
animations:^{imageView.rect = origImgRect;}
completion:^(BOOL finished){ }];