Introduction
When you add additional layers to a UIView
, those new layers don't animate in the same way that the UIView
's Backing Layer does. I have a trick that fixes this gotcha good and proper.
Background
An implicit animation is an animation which happens automatically when changing a property on a view or layer.
Every UIView
has a main/backing CALayer
. When a property of a CALayer
is changed, it queries its delegate
(the UIView
) for an animation to use for that change (actionForLayer:forKey:
) and since a view is the delegate for its layer, the layer queries its view for any custom animations actions that would be required. The view manages all its layers' implicit actions.
This all happens automatically with a normal UIView
-> CALayer
relationship. However if you add additional layers to an existing view, the new layers don't have any delegate
set.
Now why not just set the new layer's delegate to the owning view, I hear you ask?
That unfortunately doesn't won't work because a layer also controls its owning view via the delegate and if there's more than one layer controlling the view, weird things start to happen.
Using the owning view is the correct idea though;
Actually, if you only send the new layer's actionForLayer:forKey:
messages to the view, then the layer applies implicit animations correctly, and the view is not effected by the additional layers.
To do this, I've created a class to use as the layer's delegate.
The Class
LGLayerActionsForwarder.h
#import <Foundation/Foundation.h>
@interface LGLayerActionsForwarder : UIView
- (instancetype) initWithView: (UIView *) view;
@property (nonatomic, readonly) UIView *view;
@end
LGLayerActionsForwarder.m
#import "LGLayerActionsForwarder.h"
@implementation LGLayerActionsForwarder
{
__weak UIView *_view;
}
- (instancetype) initWithView: (UIView *) view
{
self = [super init];
if (!self) return nil;
_view = view;
return self;
}
- (id <CAAction>) actionForLayer: (CALayer *) layer forKey: (NSString *) event
{
return [_view actionForLayer: layer forKey: event];
}
@end
How to Use the Class
Create an instance of the LGLayerActionsForwarder
class and assign it to all the layers you create.
_actionsForwarder = [[LGLayerActionsForwarder alloc] initWithView: self];
_yellowLayer = [CALayer layer];
_yellowLayer.delegate = _actionsForwarder;
In the CustomViewLayer
example _yellowLayer
is aligned to the view's frame on layoutSubviews
, but would normally animate when the view is re-layed out because of a size change.
But, by assigning a delegate to the layer...
_yellowLayer.delegate = _actionsForwarder;
...the layer stop's animating on every change and behaves the same as the view's backing layer, which only animates within animation block. ([UIView animateWithDuration:animations:]
)
@implementation CustomViewWithLayer
{
CALayer *_yellowLayer;
LGLayerActionsForwarder *_actionsForwarder;
}
- (id) initWithCoder: (NSCoder *) coder
{
self = [super initWithCoder: coder];
if (!self) return nil;
_actionsForwarder = [[LGLayerActionsForwarder alloc] initWithView: self];
_yellowLayer = [CALayer layer];
_yellowLayer.borderColor = [UIColor blueColor].CGColor;
_yellowLayer.borderWidth = 1;
_yellowLayer.cornerRadius = 20;
_yellowLayer.delegate = _actionsForwarder;
_yellowLayer.backgroundColor = [UIColor yellowColor].CGColor;
[self.layer addSublayer: _yellowLayer];
return self;
}
- (void) layoutSubviews
{
[super layoutSubviews];
_yellowLayer.frame = self.bounds;
}
@end
History