CALayer animates with frame change? - ios

I have a CALayer I've added to my view:
myView.myCALayer = [[CALayer alloc] init];
CGSize size = myView.frame.size;
myView.myCALayer.frame = CGRectMake(0, 0, size.width, size.height);
myView.myCALayer.backgroundColor = [[UIColor blackColor] CGColor];
[myView.layer addSublayer:myView.myCALayer];
When I attempt to change the frame of the CALayer after changing the frame of myView, the resize of the CALayer animates. I have added no animation to the CALayer, so I don't understand this. I've even attempted to call removeAllAnimations on the layer before the resize and it still animates the resize.
Anyone know what could be going on here?

There is actually an implicit animation on setting some values for a CALayer. You have to disable the animations before you set a new frame.
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
[myView.myCALayer.frame = (CGRect){ { 10, 10 }, { 100, 100 } ];
[CATransaction commit];
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreAnimation_guide/AdvancedAnimationTricks/AdvancedAnimationTricks.html#//apple_ref/doc/uid/TP40004514-CH8-SW3

In Swift 4:
CATransaction.begin()
CATransaction.setDisableActions(true)
/* Your layer / frame changes here */
CATransaction.commit()

Related

Why CALayer and CAShapeLayer behaving differently

First I created the CAShapeLayer for masking the UIView at the bottom with height of 10px like this:
CAShapeLayer *mask = [[CAShapeLayer alloc]init];
CGRect maskRect = CGRectMake(0, self.view.frame.size.height-10, self.view.frame.size.width, 10.0);
CGPathRef path = CGPathCreateWithRect(maskRect, NULL);
mask.path = path;
CGPathRelease(path);
mask.anchorPoint = CGPointMake(0.5, 1.0);
self.view.layer.mask = mask;
After commenting the above code I created a CALayer for masking the UIView at the bottom with height of 10px like this:
CALayer *mask = [CALayer layer];
mask.contents = (id)[[self getImg]CGImage];
mask.frame = CGRectMake(0, self.view.frame.size.height-5, self.view.frame.size.width, 10.0);
mask.anchorPoint = CGPointMake(0.5, 1.0);
self.view.layer.mask = mask;
Question 1:
To perfectly touch the bottom not a pixel above or below, for CAShapeLayer the CGRect I defined is
CGRectMake(0, self.view.frame.size.height-10, self.view.frame.size.width, 10.0);
and for CALayer the CGRect I defined is
CGRectMake(0, self.view.frame.size.height-5, self.view.frame.size.width, 10.0);
why -10 for CAShapeLayer and why -5 for CALayer ?
The animation code and its effects on CAShapeLayer and CALayer:
CGRect oldBounds = mask.bounds;
CGRect newBounds = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height/2);
[UIView animateWithDuration:1.0 animations:^{
CABasicAnimation* revealAnimation = [CABasicAnimation animationWithKeyPath:#"bounds"];
revealAnimation.fromValue = [NSValue valueWithCGRect:oldBounds];
revealAnimation.toValue = [NSValue valueWithCGRect:newBounds];
revealAnimation.duration = 0.5;
[mask addAnimation:revealAnimation forKey:#"revealAnimation"];
}];
// Update the bounds so the layer doesn't snap back when the animation completes.
mask.bounds = newBounds;
Effect on CAShapeLayer the position is changed but no effect on hight like this:
Effect on CALayer which worked perfectly and the result I wanted:
Question 2:
Why CAShapeLayer changed the position not the height?
I don't know why but I am having a feeling like CALayer animation is not going smooth?
Question 3:
The [UIView animateWithDuration:1.0 animations:^{ ... block do not have any effect on animation, why?
Answer 1:
Not sure why it is 5 and 10 for CAShapeLayer and CALayer, need to debug closely, but maybe problem is in adjusting anchorPoint or because you use path of shape layer
Answer 2:
About effect on CAShapeLayer: this is because you assigned CGPath to it, and changed shape layer frame, but not path. So you need to set new path property with correct coordinates. Something like this:
CGRect maskRect = CGRect newBounds = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height/2);
CGPathRef path = CGPathCreateWithRect(maskRect, NULL);
mask.path = path;
CGPathRelease(path);
Answer 3:
CABasicAnimation is class, which will do all animation, and you don't need to use it in UIView animateWithDuration block.
This code should work as expected:
CABasicAnimation* revealAnimation = [CABasicAnimation animationWithKeyPath:#"bounds"];
revealAnimation.fromValue = [NSValue valueWithCGRect:oldBounds];
revealAnimation.toValue = [NSValue valueWithCGRect:newBounds];
revealAnimation.duration = 0.5;
[mask addAnimation:revealAnimation forKey:#"revealAnimation"];

CALayer draw outside of rect

I'm using this code to add a layer to a custom UIView and it works like a charm:
CGRect newrect = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
CALayer* heartBackground = [CALayer layer];
heartBackground.contents = (__bridge id)([UIImage imageNamed:#"5HeartsGray"].CGImage);
heartBackground.frame = newrect;
[self.layer addSublayer:heartBackground];
But when I tried to use it in draw method using Quartz using a new Rect(like this)
CGContextSaveGState(context);
CGRect ratingRect = CGRectMake(250, 100, 150, 20);
CALayer* heartBackground = [CALayer layer];
heartBackground.contents = (__bridge id)([UIImage imageNamed:#"5HeartsGray"].CGImage);
heartBackground.frame = ratingRect;
[heartBackground renderInContext:context];
CGContextRestoreGState(context);
It renders in the beginning of the frame and not inside of ratingRect.
If I call [heartBackground setNeedsDisplay] it disappear. The same thing with heartBackground.masksToBounds = YES
What I'm doing wrong. Do I need To switch to CGLayer because I'm using CoreGraphics?
This is the output when working with CoreGraphics (as you see the hearts starts at coordinates x=0, y=0 and normally it starts at x=250, y=100):
It doesn't make sense to create and draw a CALayer inside draw function of a UIView. CALayer is an independently drawable unit and it draws itself on its parentLayer's context by default. Which means if you really want to use separate CALayers try adding as many subLayers to self.layer (inside a UIView) as you want, set their frames and they'll get drawn when their container view shows up. Regarding your problem, here's what you can try.
Option 1: Take this code
CGRect ratingRect = CGRectMake(250, 100, 150, 20);
CALayer* heartBackground = [CALayer layer];
heartBackground.contents = (__bridge id)([UIImage imageNamed:#"5HeartsGray"].CGImage);
heartBackground.frame = ratingRect;
[self.layer addSubLayer:heartBackground];
Out of the drawRect: method and write it in initWithFrame: and in drawRect simply do [heartBackground renderInContext:UIGraphicsGetCurrentContext];
Option 2: Draw your hearts without a CALayer.
CGRect ratingRect = CGRectMake(250, 100, 150, 20);
[[UIImage imageNamed:#"5HeartsGray"] drawInRect:ratingRect];

Animating UIView and Mask

I have a UIImageView which is basically a mole. I want this mole to pop out of a hole. So actually what I have in mind is that I will create a mole some points below the hole and have a mask over it so and then animate up the image view so it looks like its popping out of hole. So following the thought , I had made written down this code :
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
CGRect maskRect = _moleIcon.frame;
CGPathRef path = CGPathCreateWithRect(maskRect, NULL);
maskLayer.path = path;
CGPathRelease(path);
_moleIcon.layer.mask = maskLayer;
[UIView animateWithDuration:2.0 animations:^(void){
_moleIcon.transform=CGAffineTransformMakeTranslation(0, 50);
}];
but the problem, is that it seems that the mask itself is moving with the actual UIImageView. Any help would be highly appreciated.
try to use 2 UIViews, just maskView should be bring to front:
[superview bringSubviewToFront:maskView];
and set background color and alpha property for mask view,
maskView.backgroundColor = [UIColor blackColor];
maskView.alpha = 0.8;
and now you can move 2 view directly.
but better is to add 2 view to other view - container, and rotate this one view.

Animating the mask for a UIView

I can create a mask like this:
CALayer *mask = [CALayer layer];
mask.contents = (id)[[UIImage imageNamed:#"mask.png"] CGImage];
mask.frame = CGRectMake(0, 0, 10, 10);
self.content.layer.mask = mask;
And this will correctly reveal the top left 10 pixels of my content (because mask.png is just a black image). However I want to animate the mask to reveal the rest of the content:
[UIView animateWithDuration:3.0
animations:^{
mask.frame = self.content.bounds;
}
completion:^(BOOL finished){
}];
The problem is that there's no animation. The entire content gets displayed immediately. Why does this happen, and how can I animate the mask so that the content is revealed from the upper left?
The frame is a derived property of various other properties, such as the position, bounds, anchorPoint, and any transform it has applied to it. It's not recommended to animate this property directly, especially when dealing with lower level CoreAnimation layers.
In this case, I would assume you want to animate the bounds. You can use the UIView animation method above, but when dealing with CALayers directly I prefer to use the CoreAnimation methods of animation.
CGRect oldBounds = mask.bounds;
CGRect newBounds = self.content.bounds;
CABasicAnimation* revealAnimation = [CABasicAnimation animationWithKeyPath:#"bounds"];
revealAnimation.fromValue = [NSValue valueWithCGRect:oldBounds];
revealAnimation.toValue = [NSValue valueWithCGRect:newBounds];
revealAnimation.duration = 3.0;
// Update the bounds so the layer doesn't snap back when the animation completes.
mask.bounds = newBounds;
[mask addAnimation:revealAnimation forKey:#"revealAnimation"];

layer with angle

how can I draw a layer with a fixed angle, instead of horizontal like this:
CALayer *layer1 = [CALayer layer];
UIImage *image1 = [UIImage imageNamed:#"cuore2.png"];
[layer1 setPosition:CGPointMake(50,50)];
[layer1 setBounds:CGRectMake(10, 10, 40, 40)];
layer1.contents=(id)image1.CGImage;
[self.view.layer addSublayer:layer1];
Thanks a lot!
If you just want to display layer rotated by angle alpha (that is don't need any pseudo 3D perspective effects) you can set layer's transform:
[CATransaction setDisableActions:YES]; // Disable animation
layer1.transform = CATransform3DMakeRotation(alpha, 0, 0, 1); // Rotate layer

Resources