I have a class that has 16 sublayers to form a logo. When I animate the UIView
the CALayers
are not animated but just go to the final state as in the animated gif below:
My code is:
@implementation LogoView
#define CGRECTMAKE(a, b, w, h) {.origin={.x=(a),.y=(b)},.size={.width=(w),.height=(h)}}
#pragma mark - Create Subviews
const static CGRect path[] = {
CGRECTMAKE(62.734375,-21.675000,18.900000,18.900000),
CGRECTMAKE(29.784375,-31.725000,27.400000,27.300000),
CGRECTMAKE(2.534375,-81.775000,18.900000,18.900000),
CGRECTMAKE(4.384375,-57.225000,27.400000,27.300000),
CGRECTMAKE(2.784375,62.875000,18.900000,18.900000),
CGRECTMAKE(4.334375,29.925000,27.400000,27.300000),
CGRECTMAKE(62.734375,2.525000,18.900000,18.900000),
CGRECTMAKE(29.784375,4.475000,27.400000,27.300000),
CGRECTMAKE(-21.665625,-81.775000,18.900000,18.900000),
CGRECTMAKE(-31.765625,-57.225000,27.400000,27.300000),
CGRECTMAKE(-81.615625,-21.425000,18.900000,18.900000),
CGRECTMAKE(-57.215625,-31.775000,27.400000,27.300000),
CGRECTMAKE(-81.615625,2.775000,18.900000,18.900000),
CGRECTMAKE(-57.215625,4.425000,27.400000,27.300000),
CGRECTMAKE(-21.415625,62.875000,18.900000,18.900000),
CGRECTMAKE(-31.765625,29.925000,27.400000,27.300000)
};
- (void) createSubviews
{
self.contentMode = UIViewContentModeRedraw;
for (int i = 0; i < 16; i++) {
CGRect rect = CGRectApplyAffineTransform(path[i],
CGAffineTransformMakeScale(self.frame.size.width / 213.0,
self.frame.size.height / 213.0));
UIBezierPath * b = [UIBezierPath bezierPathWithOvalInRect:
CGRectOffset(rect,
self.frame.size.width/2.0,
self.frame.size.height/2)];
CAShapeLayer * layer = [[CAShapeLayer alloc] init];
layer.path = [b CGPath];
layer.fillColor = [self.tintColor CGColor];
[self.layer addSublayer:layer];
}
self.layer.needsDisplayOnBoundsChange = YES;
self.initialLenght = self.frame.size.width;
}
- (void) layoutSublayersOfLayer:(CALayer *)layer
{
for (int i = 0; i < 16; i++) {
CGRect rect = CGRectApplyAffineTransform(path[i],
CGAffineTransformMakeScale(layer.frame.size.width / 213.0,
layer.frame.size.height / 213.0));
UIBezierPath * b = [UIBezierPath
bezierPathWithOvalInRect:CGRectOffset(rect,
layer.frame.size.width/2.0,
layer.frame.size.height/2)];
((CAShapeLayer*)(layer.sublayers[i])).path = b.CGPath;
}
}
- (void)layoutSubviews {
[super layoutSubviews];
// get current animation for bounds
CAAnimation *anim = [self.layer animationForKey:@"bounds"];
[CATransaction begin];
if(anim) {
// animating, apply same duration and timing function.
[CATransaction setAnimationDuration:anim.duration];
[CATransaction setAnimationTimingFunction:anim.timingFunction];
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
[self.layer addAnimation:pathAnimation forKey:@"path"];
}
else {
// not animating, we should disable implicit animations.
[CATransaction disableActions];
}
self.layer.frame = self.frame;
[CATransaction commit];
}
And I'm animating with:
[UIView animateWithDuration:3.0 animations:^{
[v setFrame:CGRectMake(0.0, 0.0, 300.0, 300.0)];
}];
How can I sync the layer animation with the view animation?
No, it is not needed in this case.
The contents of your block are performed on the main thread regardless of where you call [UIView animateWithDuration:animations:] .
Core Animation uses action objects to implement the default set of animations normally associated with layers. You can create your own action objects to implement custom animations or use them to implement other types of behaviors too. You then assign your action object to one of the layer's properties.
Animating layer properties in sync with UIView animation can be tricky. Instead, let's use a different view structure to take advantage of the built-in support for animating a view's transform
, instead of trying to animate a layer's path
.
We'll use two views: a superview and a subview. The superview is a LogoView
and is what we lay out in the storyboard (or however you create your UI). The LogoView
adds a subview to itself of class LogoLayerView
. This LogoLayerView
uses a CAShapeLayer
as its layer instead of a plain CALayer
.
Note that we only need one CAShapeLayer
, because a path can contain multiple disconnected regions.
We set the frame/bounds of the LogoLayerView
once, to CGRectMake(0, 0, 213, 213)
, and never change it. Instead, when the outer LogoView
changes size, we set the LogoLayerView
's transform
so that it still fills the outer LogoView
.
Here's the result:
Here's the code:
#import <UIKit/UIKit.h>
IB_DESIGNABLE
@interface LogoView : UIView
@end
#import "LogoView.h"
#define CGRECTMAKE(a, b, w, h) {.origin={.x=(a),.y=(b)},.size={.width=(w),.height=(h)}}
const static CGRect ovalRects[] = {
CGRECTMAKE(62.734375,-21.675000,18.900000,18.900000),
CGRECTMAKE(29.784375,-31.725000,27.400000,27.300000),
CGRECTMAKE(2.534375,-81.775000,18.900000,18.900000),
CGRECTMAKE(4.384375,-57.225000,27.400000,27.300000),
CGRECTMAKE(2.784375,62.875000,18.900000,18.900000),
CGRECTMAKE(4.334375,29.925000,27.400000,27.300000),
CGRECTMAKE(62.734375,2.525000,18.900000,18.900000),
CGRECTMAKE(29.784375,4.475000,27.400000,27.300000),
CGRECTMAKE(-21.665625,-81.775000,18.900000,18.900000),
CGRECTMAKE(-31.765625,-57.225000,27.400000,27.300000),
CGRECTMAKE(-81.615625,-21.425000,18.900000,18.900000),
CGRECTMAKE(-57.215625,-31.775000,27.400000,27.300000),
CGRECTMAKE(-81.615625,2.775000,18.900000,18.900000),
CGRECTMAKE(-57.215625,4.425000,27.400000,27.300000),
CGRECTMAKE(-21.415625,62.875000,18.900000,18.900000),
CGRECTMAKE(-31.765625,29.925000,27.400000,27.300000)
};
#define LogoDimension 213.0
@interface LogoLayerView : UIView
@property (nonatomic, strong, readonly) CAShapeLayer *layer;
@end
@implementation LogoLayerView
@dynamic layer;
+ (Class)layerClass {
return [CAShapeLayer class];
}
- (void)layoutSubviews {
[super layoutSubviews];
if (self.layer.path == nil) {
[self initShapeLayer];
}
}
- (void)initShapeLayer {
self.layer.backgroundColor = [UIColor yellowColor].CGColor;
self.layer.strokeColor = nil;
self.layer.fillColor = [UIColor greenColor].CGColor;
UIBezierPath *path = [UIBezierPath bezierPath];
for (size_t i = 0; i < sizeof ovalRects / sizeof *ovalRects; ++i) {
[path appendPath:[UIBezierPath bezierPathWithOvalInRect:ovalRects[i]]];
}
[path applyTransform:CGAffineTransformMakeTranslation(LogoDimension / 2, LogoDimension / 2)];
self.layer.path = path.CGPath;
}
@end
@implementation LogoView {
LogoLayerView *layerView;
}
- (void)layoutSubviews {
[super layoutSubviews];
if (layerView == nil) {
layerView = [[LogoLayerView alloc] init];
layerView.layer.anchorPoint = CGPointZero;
layerView.frame = CGRectMake(0, 0, LogoDimension, LogoDimension);
[self addSubview:layerView];
}
[self layoutShapeLayer];
}
- (void)layoutShapeLayer {
CGSize mySize = self.bounds.size;
layerView.transform = CGAffineTransformMakeScale(mySize.width / LogoDimension, mySize.height / LogoDimension);
}
@end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With