I have a UIImageView with image just like above so what i have to do is gradually decrease and increase the shape of the image.
For achieving above task obviously I have to apply mask so I have created a rounded CAShapeLayer and added to the UIImageView layer and its working fine if I change my radius it will show only that amount of image wrt radius.
My problem is how we can apply animation so it will show in animated form. Please guide me I am not able to achieve with keyframe animation.
Following are the code for masking wrt radius.
// Set up the shape of the circle
int rChange = 0; // Its doing proper masking while changing this value
int radius = 123.5-rChange;
CAShapeLayer *circle = [CAShapeLayer layer];
// Make a circular shape
circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0+rChange, 0+rChange, 2.0*radius, 2.0*radius)
cornerRadius:radius].CGPath;
// Configure the apperence of the circle
[circle setFillColor:[[UIColor blackColor] CGColor]];
[[self.originalImageView layer] setMask:circle];
self.originalImageView.layer.masksToBounds = YES;
where 123.5 is my maximum radius of the image
and originalImageView is myUIImageView
If all you want to do is show a brown circle with an animated varying radius, I suggest two changes:
Don't bother with an image. Just use a CAShapeLayer and set its fillColor to brown.
Set the layer's path once, to a circle with width and height of 1 point. Then use the CAShapeLayer's transform to vary the size. You can animate the transform.
For example, create the layer like this:
CGRect bounds = self.view.bounds;
circleLayer = [CAShapeLayer layer];
circleLayer.fillColor = [UIColor brownColor].CGColor;
circleLayer.position = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
// Create a circle with 1-point width/height.
circleLayer.path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 1, 1)].CGPath;
// Use the layer transform to scale the circle up to the size of the view.
[circleLayer setValue:#(bounds.size.width) forKeyPath:#"transform.scale"];
Then you can change its size like this:
[circleLayer setValue:#(newSize) forKeyPath:#"transform.scale"];
That will implicitly (automatically) animate the size change using default parameters. If you want to use different animation parameters, you can explicitly animate the transform:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
animation.fromValue = [circleLayer valueForKeyPath:#"transform.scale"];
animation.toValue = #(newSize);
animation.duration = 3.0;
animation.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut];
// Important: change the actual layer property before installing the animation.
[circleLayer setValue:animation.toValue forKeyPath:animation.keyPath];
// Now install the explicit animation, overriding the implicit animation.
[circleLayer addAnimation:animation forKey:animation.keyPath];
Related
I am trying to make a circle which will be animated and filled with black color clockwise, the duration of the circle animating is predefined to 20 second, what I want to do is the first 10 seconds it will be filled with black and the rest of the circle will be filled with red, and after the total 20 seconds it will become a 2 colored round circle, but I couldnt find any procedure of it, here is my code by which I drawn the circle animation:
-(void)makeCircle{
int radius = 100;
CAShapeLayer *circle = [CAShapeLayer layer];
//make a circular shape
circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius)cornerRadius:radius].CGPath;
//centre the shape in self.view
circle.position = CGPointMake(CGRectGetMidX(self.view.frame)-radius, CGRectGetMidY(self.view.frame)-radius);
//configure the appearence of the circle
circle.fillColor = [UIColor clearColor].CGColor;
circle.strokeColor =[UIColor blackColor].CGColor;
circle.lineWidth = 5;
//add to parent layer
[self.view.layer addSublayer:circle];
//configure animation
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = 20.0;//animate over 10 seconds or so...
drawAnimation.repeatCount=1.0;//animate only once;
drawAnimation.removedOnCompletion=NO;
drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
drawAnimation.toValue = [NSNumber numberWithFloat:1.0f];
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[circle addAnimation:drawAnimation forKey:#"drawCircleAnimation"];
}
but I need to know how should I do this?
Have you looked at This?
I think it might help.
I am trying to add a CALayer as a sublayer of another CALayer. However only the parent layer gets displayed. Here's my code:
//display a green square:
CALayer *shipContainer = [CALayer layer];
shipContainer.bounds = CGRectMake(0,0,200,200);
shipContainer.position = CGPointMake(600,500);
shipContainer.borderColor = [UIColor greenColor].CGColor;
shipContainer.borderWidth = 3;
//display a red dot inside the square:
CALayer *ship1 = [CALayer layer];
ship1.bounds = CGRectMake(0,0,20,20);
ship1.position = CGPointMake(600,500);
ship1.cornerRadius = 10;
ship1.backgroundColor = [UIColor redColor].CGColor;
[shipContainer addSublayer:ship1];
I then call [self.view.layer addSublayer:shipContainer]; but only the green square is displayed. Any thoughts?
As per documentation :
Position
The position property is a CGPoint that specifies the position of the
layer relative to its superlayer, and is expressed in the superlayer's
coordinate system.
so you need to change
ship1.position = CGPointMake(600,500);
so that ship1 can come in visible area. As superlayer has 200,200 as its bounds you need to make position's x and y less then these values.
I am creating a layer with a shadow effect. Instead of adding it as a sublayer to my view's layer, I would like to draw it. But I am having problems being able to draw it with the shadow effect. The problem is that the context size it based on the layer size. But how do I set the rect for the context?!
CALayer *layer = [CALayer layer];
layer.frame = ...;
layer.backgroundColor = [UIColor redColor].CGColor;
layer.cornerRadius = 2.0;
layer.masksToBounds = NO;
layer.shadowColor = [[UIColor whiteColor] CGColor];
layer.shadowOffset = CGSizeMake(0, 1);
layer.shadowRadius = 0.5;
layer.shadowOpacity = 0.2;
[self.layer addSublayer:layer];
UIGraphicsBeginImageContext(layer.bounds.size);
[layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[viewImage drawAtPoint:CGPointMake(0, 0)];
[layer removeFromSuperlayer];
With the code above, what I am getting is the box, but without a few extra pixels of padding with the shadow (on all four sides). If I increase the size of the ImageContext, all I get is more height and width, but still starting from x=0 and y=0, where I would want it to start from x=-5, y=-5 or something like that.
Thanks a bunch!
Make your context 10 pixels larger in both the x and the y.
Then before you draw your layer do a CGContextTranslateCTM(context,5,5);
Hope this helps!
I'm not sure if the following is your problem, but it just caught my eye.
[self.layer addSublayer:layer];
UIGraphicsBeginImageContext(layer.bounds.size);
[layer renderInContext:UIGraphicsGetCurrentContext()];
It looks like you have a property on this file called layer, which you are adding the shadow layer you've created to. You then get the context based on the current CALayer, as opposed to self.layer.
I typically use CALayer to modify UIImageView, and do not have a need to add sub layers, so perhaps that code is fine. Just wanted to point it out, please do let me know if which way is accurate, as I always like to learn.
~Good Luck
I would like to create a CALayer bezier path in a circle shape. And I would like to set it like it was a UIImageView (to be able to move it with animation etc.) Is it possible? If so how can I do it? Thank you.
You could always add a CAShapeLayer to your view. During init, do something like (all untested):
CAShapeLayer *circle = [[CAShapeLayer alloc] init];
[self.layer addSublayer:circle];
Then, in drawRect, layoutSubViews, or some other place that seems appropriate to update the layout:
circle.fillColor = [[UIColor redColor].CGColor;
CGMutablePathRef p = CGPathCreateMutable();
CGPathAddEllipseInRect(p, NULL, self.bounds);
circle.path = p;
circle.bounds = self.bounds;
circle.position = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
I'm not sure what you mean by a "CALayer bezier path" but you have a couple of options here:
Create a subclass of UIView, where you draw your circle using UIBezierPath methods in the drawRect:. This view can then be added to another view as a subview and animated using standard methods. However, you are warned that this may affect performance, (as it has to keep re-drawing) so if that does turn out to be a problem...
Create a graphics context and draw your circle in there. Take an image from this graphics context and set it as the image for a UIImageView. Animate as in answer (1). This way you aren't redrawing all the time so performance would be better, but try with option 1 first as it is simpler, and if you have simple drawing code it shouldn't affect you too much.
For option 2, you begin a graphics context using UIGraphicsBeginImageContextWithOptions - you then do your drawing, and extract the image using UIGraphicsGetImageFromCurrentImageContext, then end the context using UIGraphicsEndImageContext. Those functions are all documented here
Sample code to do this:
CGFloat radius = 50;
CGSize size = CGSizeMake(radius*2,radius*2);
CGPoint centre = CGPointMake(radius,radius);
UIGraphicsBeginImageContextWithOptions(size,NO, 0.0);
UIBezierPath * solidPath = [UIBezierPath bezierPathWithArcCenter:centre radius:edgeSize/20 startAngle:0 endAngle:2 * M_PI clockwise:YES];
[solidPath closePath];
[[UIColor whiteColor] set];
[solidPath fill];
UIImage *circle = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *imageView = [[UIImageView alloc] initWithImage:circle];
I'm trying to create a simple iPhone app that displays a picture with a reflection beneath, and I want to rotate the picture around the X axis, using Core Animation.
I've started by creating a new iPhone app using the "View-based Application" template. I added a "Picture.jpg" image file, and then I added this to the view controller:
- (void)awakeFromNib {
CGFloat zDistance = 1500.0f;
// Create perspective transformation
CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.0f / -zDistance;
// Create perspective transform for reflected layer
CATransform3D reflectedTransform = CATransform3DMakeRotation(M_PI, 1.0f, 0.0f, 0.0f);
reflectedTransform.m34 = 1.0f / -zDistance;
// Create spinning picture
CALayer *spinningLayer = [CALayer layer];
spinningLayer.frame = CGRectMake(60.0f, 60.0f, 200.0f, 300.0f);
spinningLayer.contents = (id)[UIImage imageNamed:#"Picture.jpg"].CGImage;
spinningLayer.transform = transform;
// Create reflection of spinning picture
CALayer *reflectionLayer = [CALayer layer];
reflectionLayer.frame = CGRectMake(60.0f, 360.0f, 200.0f, 300.0f);
reflectionLayer.contents = (id)[UIImage imageNamed:#"Picture.jpg"].CGImage;
reflectionLayer.opacity = 0.4f;
reflectionLayer.transform = reflectedTransform;
// Add layers to the root layer
[self.view.layer addSublayer:spinningLayer];
[self.view.layer insertSublayer:reflectionLayer below:spinningLayer];
// Spin the layers
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
anim.fromValue = [NSNumber numberWithFloat:0.0f];
anim.toValue = [NSNumber numberWithFloat:(2 * M_PI)];
anim.duration = 3.0f;
anim.repeatCount = 1e100f;
[spinningLayer addAnimation:anim forKey:#"anim"];
[reflectionLayer addAnimation:anim forKey:#"anim"];
}
This almost works. The problem is that the spinning of the main picture and of the reflection are not perfectly synchronized. It's very close to perfect, but the edges of the bottom of the picture and of top of the reflection are a few pixels apart. They appear to differ by a few degrees.
On the left side of the screen, the reflection seems to be "ahead" of the picture, and on the right side, the reflection is behind the picture. In other words, it looks like the bottom corners of the top image are pushed toward the screen, while the top corners of the reflection are pulled toward the viewer. This is true both when looking at the front and at the back of the images as they spin.
If I increase zDistance, the effect is less noticeable. If I eliminate the perspective transformations altogether by leaving transform.m34 equal to zero for both layers, then the picture and reflection look to be perfectly synchronized. So I don't think the problem is related to time-synchronization issues.
I suspect my perspective transformations are missing something, but I'm not sure how to determine what's wrong. I think I'm basically doing the same transformation described in this related Stack Overflow question.
Can someone help?
I think I may have found an answer to my question. In my original code, I created the reflection by placing it underneath the picture, and applying the flip and perspective. The right way to do it seems to be to place the reflection at the same position as the picture, apply translation and rotation transforms to get it to the right place, and then apply the perspective transformation.
When animating the spin, it is necessary to spin the reflection in the opposite direction of the picture's animation.
So, here's code that works:
- (void)awakeFromNib {
CGFloat zDistance = 1500.0f;
// Create perspective transformation
CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.0f / -zDistance;
// Create perspective transform for reflected layer
CATransform3D reflectedTransform = CATransform3DMakeTranslation(0.0f, 300.0f, 0.0f);
reflectedTransform = CATransform3DRotate(reflectedTransform, M_PI, 1.0f, 0.0f, 0.0f);
reflectedTransform.m34 = 1.0f / -zDistance;
// Create spinning picture
CALayer *spinningLayer = [CALayer layer];
spinningLayer.frame = CGRectMake(60.0f, 60.0f, 200.0f, 300.0f);
spinningLayer.contents = (id)[UIImage imageNamed:#"Picture.jpg"].CGImage;
spinningLayer.transform = transform;
// Create reflection of spinning picture
CALayer *reflectionLayer = [CALayer layer];
reflectionLayer.frame = CGRectMake(60.0f, 60.0f, 200.0f, 300.0f);
reflectionLayer.contents = (id)[UIImage imageNamed:#"Picture.jpg"].CGImage;
reflectionLayer.opacity = 0.4f;
reflectionLayer.transform = reflectedTransform;
// Add layers to the root layer
[self.view.layer addSublayer:spinningLayer];
[self.view.layer insertSublayer:reflectionLayer below:spinningLayer];
// Spin the layers
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
anim.fromValue = [NSNumber numberWithFloat:0.0f];
anim.toValue = [NSNumber numberWithFloat:(2 * M_PI)];
anim.duration = 3.0f;
anim.repeatCount = 1e100f;
[spinningLayer addAnimation:anim forKey:#"anim"];
CABasicAnimation *reflectAnim = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
reflectAnim.fromValue = [NSNumber numberWithFloat:0.0f];
reflectAnim.toValue = [NSNumber numberWithFloat:(-2 * M_PI)];
reflectAnim.duration = 3.0f;
reflectAnim.repeatCount = 1e100f;
[reflectionLayer addAnimation:reflectAnim forKey:#"reflectAnim"];
}
I'm not sure exactly why this works. It makes intuitive sense to me that placing the reflection at the same position as the original and then transforming it somehow puts the two images into the same "transformation space", but I'd appreciate it if someone could explain the mathematics behind this.
The full project is available at GitHub: https://github.com/kristopherjohnson/perspectivetest
You can use a CAAnimationGroup object to run two animations on the same time space. That should take care of any synchronization issues.
EDIT: removed silly code.