(iOS) How to animate rounded rectangle with shapeLayer? - ios

I am trying to animate the width of a rounded rectangle, the problem is when going from bigger width to thinner width, the animation does an "aberration ease jump".
Here's the code:
shapeLayer = [CAShapeLayer layer];
shapeRect = CGRectMake(0.0f, 0.0f, 150.0f, 200.0f);
[shapeLayer setBounds:shapeRect];
[shapeLayer setPosition:CGPointMake(iniPosX, 80.0f)];
[shapeLayer setFillColor:[[UIColor blackColor] CGColor]];
[shapeLayer setStrokeColor:[[UIColor clearColor] CGColor]];
[shapeLayer setLineWidth:1.0f];
[shapeLayer setLineJoin:kCALineJoinRound];
[shapeLayer setOpacity:0.2];
path = [UIBezierPath bezierPathWithRoundedRect:shapeRect cornerRadius:15.0];
[shapeLayer setPath:path.CGPath];
[self.layer addSublayer:shapeLayer];
And when I start the animation:
- (void)adjustSelectorToPosAndSize:(float)posX andWidth:(float)width
{
shapeRect = CGRectMake(0.0f, 0.0f, width, 200.0f);
[shapeLayer setBounds:shapeRect];
[shapeLayer setPosition:CGPointMake(posX, 80.0f)];
path = [UIBezierPath bezierPathWithRoundedRect:shapeRect cornerRadius:15.0];
[shapeLayer setPath:path.CGPath];
}
What am I doing wrong?

Doing this with a CAShapeLayer seems overly complicated to me if you are only using it for a rounded rectangle. Why not simply use a CALayer with the cornerRadius properly set?
Animating the frame with cornerRadius set will work fine.
myLayer = [CALayer layer];
shapeRect = CGRectMake(0.0f, 0.0f, 150.0f, 200.0f);
[myLayer setBounds:shapeRect];
[myLayer setPosition:CGPointMake(iniPosX, 80.0f)];
[myLayer setBackgroundColor:[[UIColor blackColor] CGColor]];
[myLayer setBorderColor:[[UIColor clearColor] CGColor]];
[myLayer setBorderWidth:1.0f];
[myLayer setOpacity:0.2];
[myLayer setCornerRadius:15.0];
[self.layer addSublayer:myLayer];
Animating it is done by simply chaining the frame (not the path)
- (void)adjustSelectorToPosAndSize:(float)posX andWidth:(float)width
{
shapeRect = CGRectMake(0.0f, 0.0f, width, 200.0f);
[myLayer setBounds:shapeRect];
[myLayer setPosition:CGPointMake(posX, 80.0f)];
}
Important:
Some of the properties changed name like fillColor became backgroundColor and the strokeColor and lineWidth became borderColor and borderWidth etc.

Although CAShapeLayer’s path property is animatable, it does not do so implicitly like bounds and position, you have to make an explicit animation:
CABasicAnimation* anim = [CABasicAnimation animationWithKeyPath: #"path"];
anim.fromValue = (id) fromPath.CGPath;
anim.toValue = (id) toPath.CGPath;
anim.duration = 1.0;
[layer addAnimation: anim forKey: #“resize”];
Since you probably want all three animations to happen together, you can make them all explicit and grouped together:
CABasicAnimation* anim1 = [CABasicAnimation animationWithKeyPath: #"position"];
anim1.fromValue = [NSValue valueWithCGPoint: fromPosition];
anim1.toValue = [NSValue valueWithCGPoint: toPosition];
anim1.duration = 1.0;
CABasicAnimation* anim2 = [CABasicAnimation animationWithKeyPath: #"bounds"];
anim2.fromValue = [NSValue valueWithCGRect: fromBounds];
anim2.toValue = [NSValue valueWithCGRect: toBounds];
anim2.duration = 1.0;
CABasicAnimation* anim3 = [CABasicAnimation animationWithKeyPath: #"path"];
anim3.fromValue = (id) fromPath.CGPath;
anim3.toValue = (id) toPath.CGPath;
anim3.duration = 1.0;
CAAnimationGroup* animG = [CAAnimationGroup animation];
animG.animations = [NSArray arrayWithObjects: anim1, anim2, anim3, nil];
animG.duration = 1.0;
[layer addAnimation: animG forKey:#"resize"];
If your shape does not have contents or children then you may be able to use a CALayer instead:
CALayer* rectLayer = [CALayer layer];
rectLayer.masksToBounds = YES;
rectLayer.bounds = startBounds;
rectLayer.backgroundColor = [[UIColor blackColor] CGColor];
rectLayer.cornerRadius = 15.0;
[self.layer addSublayer: rectLayer];
// Then later implicitly animate the bounds:
rectLayer.bounds = newBounds;

Related

UICollectionViewCell dashed border

I want to make my custom UICollectionViewCell have a dashed border around it, I have tried using layer and bezier path for it but none of the approach has helped.
In you UICollectionViewCell's Subclass
static CGFloat const kDashedBorderWidth = (2.0f);
static CGFloat const kDashedPhase = (0.0f);
static CGFloat const kDashedLinesLength[] = {4.0f, 2.0f};
static size_t const kDashedCount = (2.0f);
-(void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, kDashedBorderWidth);
CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
CGContextSetLineDash(context, kDashedPhase, kDashedLinesLength, kDashedCount) ;
CGContextAddRect(context, rect);
CGContextStrokePath(context);
}
Add this method in your class.
- (CAShapeLayer *) addDashedBorderWithColor: (CGColorRef)color withSize:(CGSize)size{
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
CGSize frameSize = size;
CGRect shapeRect = CGRectMake(0.0f, 0.0f, frameSize.width, frameSize.height);
[shapeLayer setBounds:shapeRect];
[shapeLayer setPosition:CGPointMake( frameSize.width/2,frameSize.height/2)];
[shapeLayer setFillColor:[[UIColor clearColor] CGColor]];
[shapeLayer setStrokeColor:color];
[shapeLayer setLineWidth:5.0f];
[shapeLayer setLineJoin:kCALineJoinRound];
[shapeLayer setLineDashPattern:
[NSArray arrayWithObjects:[NSNumber numberWithInt:10],
[NSNumber numberWithInt:5],
nil]];
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:shapeRect cornerRadius:15.0];
[shapeLayer setPath:path.CGPath];
return shapeLayer;
}
Write below line code in your cellForRowAtIndexPath method.
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier"#"cell"];
//Do what you want to set in your collectionViewCell
CAShapeLayer *maskLayer= [self addDashedBorderWithColor:[UIColor whiteColor].CGColor withSize:cell.borderView.frame.size];
cell.layer.mask = maskLayer;

CAShapeLayer jittery animation in iOS

I am trying to animate loading animation along the path of rounded rect,using following code. (I need to animate border of square as loader ,partial border.) It does not happen to be smooth ,as soon as it reaches the end of path it jumps to the starting start stroke.
shapeLayer = [CAShapeLayer layer];
CGRect shapeRect = _border.bounds;//CGRectMake(0.0f, 0.0f, 200.0f, 100.0f);
[shapeLayer setBounds:shapeRect];
[shapeLayer setContents:_border.image];
shapeLayer.contentsGravity = kCAGravityCenter;
NSLog(#"center of border %#",NSStringFromCGPoint(_border.center) );
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:shapeLayer.bounds cornerRadius:BORDER_ANGLE];
[shapeLayer setPath:path.CGPath];
shapeLayer.position = _border.center;
[[self.view layer] addSublayer:shapeLayer];
[[self.view layer] insertSublayer:shapeLayer above:_border.layer];
[shapeLayer setFillColor:[[UIColor clearColor] CGColor]];
[shapeLayer setStrokeColor:[[UIColor whiteColor] CGColor]];
[shapeLayer setLineWidth:2.5f];
shapeLayer.strokeStart = 0;
shapeLayer.strokeEnd = 0.1;
UIImageView * image = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"ball"]];
image.center = CGPointMake(187, 220.5);
startAnim = [CABasicAnimation animationWithKeyPath:#"strokeStart"];
startAnim.duration = SPEED;
startAnim.delegate = self;
startAnim.repeatCount=200;
startAnim.removedOnCompletion = FALSE;
startAnim.toValue = [NSNumber numberWithFloat:0.3];
endAnim = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
endAnim.toValue = [NSNumber numberWithFloat:0.99];
endAnim.duration = SPEED;
endAnim.repeatCount = 200;
endAnim.removedOnCompletion = NO;
endAnim.cumulative = NO;
[shapeLayer addAnimation:startAnim forKey:nil];
[shapeLayer addAnimation:endAnim forKey:nil];
But above code jitters after reaching at endAnim's tovalue and starts again,its not smooth.
Please help me know which parts going wrong. I saw many tutorials for loader but all those were for circular path not square.
[enter image description here][1]
! [1]: https://i.stack.imgur.com/FwT01.png
just want to fill 50% of border and rotate it continuously
Okay. So do not use animation of a shape layer. Draw the square. Now make a mask that is a filled square with half of it empty. Place the mask on your square. So now we see only 50% of your square because the rest is masked out. Got it?
Now rotate the mask.

CAShapeLayer shrink and fade out animation not working correctly

I have this circle (that fits inside the letter O which is currently not exactly a circle) and I want it to shrink and regrow, fading out and in. So I have it shrinking, and fading out, but when it does the animation, the CAShapeLayer moves to the top left of the screen and doesn't stay put in place (which is the objective). How do I accomplish this?
CAShapeLayer *circleLayer = [CAShapeLayer layer];
[circleLayer setPath:[[UIBezierPath bezierPathWithOvalInRect:CGRectMake(74.93, 59.5, 31.52, 31.2)] CGPath]];
[circleLayer setStrokeColor:[[UIColor colorWithRed:0.8 green:0.1 blue:0.2 alpha:1.0] CGColor]];
[circleLayer setFillColor:[[UIColor colorWithRed:0.8 green:0.1 blue:0.2 alpha:1.0] CGColor]];
CABasicAnimation* fadeAnim = [CABasicAnimation animationWithKeyPath:#"opacity"];
CABasicAnimation* shrinkAnim = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
fadeAnim.fromValue = [NSNumber numberWithFloat:1.0];
fadeAnim.toValue = [NSNumber numberWithFloat:0.0];
fadeAnim.duration = 1.25;
shrinkAnim.fromValue = [NSNumber numberWithFloat:1.0];
shrinkAnim.toValue = [NSNumber numberWithFloat:0.05];
shrinkAnim.duration = 1.25;
[circle1 addAnimation:fadeAnim forKey:#"opacity"];
[circle1 addAnimation:shrinkAnim forKey:#"transform.scale"];
circle1.opacity = 0.0;

how to create a line graph with multiple lines and different colors using bezierpath

I created one bezierpath and added that to cashapelayer but i cant able to change the color of path
//create path
UIBezierPath *graphPath = [[UIBezierPath alloc]init];
[graphPath setLineWidth:30];
[[UIColor blackColor] setStroke];
[graphPath moveToPoint:CGPointMake(30 , 30)];
[graphPath addLineToPoint:CGPointMake(30,600)];
[graphPath addLineToPoint:CGPointMake(380, 600)];
//adding path to layer
CAShapeLayer *graphLayout = [CAShapeLayer layer];
graphLayout.frame = CGRectMake(0, 65, VIEW_WIDTH, VIEW_HEIGHT);
graphLayout.fillColor = [[UIColor clearColor] CGColor];
graphLayout.strokeColor = [UIColor redColor].CGColor;
graphLayout.lineWidth = 2;
graphLayout.path = [graphPath CGPath];
[self.layer addSublayer:graphLayout];
//animation
drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = 3;
drawAnimation.repeatCount = 1.0;
drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
drawAnimation.toValue = [NSNumber numberWithFloat:1.0f];
[graphLayout addAnimation:drawAnimation forKey:#"drawCircleAnimation"];

How to put 2 layers to follow same bezierpath without one hiding the other

I have created 2 CALayers of same size and am passing these to the method below. However, the two layers runs together. How can I seperate these so that both are visible?
- (void) myAnimation : (CALayer *) sublayer {
UIBezierPath* aPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(30, 100, 270, 270)];
CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:#"position"];
anim.path = aPath.CGPath;
anim.rotationMode = kCAAnimationRotateAuto;
anim.repeatCount = HUGE_VALF;
anim.duration =35.0;
[sublayer addAnimation:anim forKey:#"race"];
}
Your path begins and ends at the same point. Assuming both beginning times are the same and duration is the same, your layers will precisely overlap. You can either shift the beginning time or rotate your UIBezierPath* aPath Here's an example of rotating your UIBezierPath* aPath and altering the duration. It should give you an idea how you could alter duration, begin time, rotation, etc.
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view setBackgroundColor:[UIColor blackColor]];
CALayer * layer1 = [CALayer layer];
CALayer * layer2 = [CALayer layer];
[layer1 setFrame:CGRectMake(0, 0, 5, 50)];
[layer2 setFrame:CGRectMake(0, 0, 5, 100)];
layer1.backgroundColor = [[UIColor colorWithRed:.1 green:.5 blue:1 alpha:.35] CGColor];
layer2.backgroundColor = [[UIColor colorWithRed:.9 green:.2 blue:.5 alpha:.35] CGColor];
[self.view.layer addSublayer:layer1];
[self.view.layer addSublayer:layer2];
[self myAnimation:layer1 andRotation:0 andDuration:35.0];
[self myAnimation:layer2 andRotation:10 andDuration:10.0];
}
- (void) myAnimation:(CALayer *)sublayer andRotation:(CGFloat)rot andDuration:(CFTimeInterval)dur {
CGRect rect = CGRectMake(30, 100, 270, 270);
UIBezierPath* aPath = [UIBezierPath bezierPathWithOvalInRect:rect];
// Creating a center point around which we will transform the path
CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformTranslate(transform, center.x, center.y);
transform = CGAffineTransformRotate(transform, rot);
// Recenter the transform
transform = CGAffineTransformTranslate(transform, -center.x, -center.y);
// This is the new path we will use.
CGPathRef rotated = CGPathCreateCopyByTransformingPath(aPath.CGPath, &transform);
CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:#"position"];
anim.path = rotated;
anim.rotationMode = kCAAnimationRotateAuto;
anim.repeatCount = HUGE_VALF;
anim.duration =dur;
[sublayer addAnimation:anim forKey:#"race"];
}

Resources