Click here to Skip to main content
15,868,016 members
Articles / Mobile Apps / iOS

iOS Tricks: UIView with Additional sublayers - Applying the Same Implicit Animation Rules that the Main Layer Uses

Rate me:
Please Sign up or sign in to vote.
4.47/5 (9 votes)
21 May 2014CPOL2 min read 23.1K   86   4   2
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.

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
Objective-C
#import <Foundation/Foundation.h>

@interface LGLayerActionsForwarder : UIView

- (instancetype) initWithView: (UIView *) view;

@property (nonatomic, readonly) UIView *view;

@end
LGLayerActionsForwarder.m
Objective-C
#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.

Objective-C
_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...

Objective-C
_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:])

Objective-C
@implementation CustomViewWithLayer
{
    CALayer *_yellowLayer;
    LGLayerActionsForwarder *_actionsForwarder;
}

- (id) initWithCoder: (NSCoder *) coder 
{
    self = [super initWithCoder: coder];
    if (!self) return nil;

    // create a forwarder instance and link it to the view
    // NOTE: If you comment out just this line, then the yellow layer would function as a normal layer (animating).
    _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

  • Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead FNB Connect
South Africa South Africa
iOS Technical Lead at FNB
-

Computers are really sweet. Aren't they?
Yup they are...

I've always loved writing tools and components...never been very interested in playing games....always wanted to be able to write them though.

And, yes. I'm still pretty annoyed they discontinued the Amiga computer.

Comments and Discussions

 
GeneralMy vote of 5 Pin
KarstenK3-Aug-18 0:21
mveKarstenK3-Aug-18 0:21 
Small but useful, really THATS what I needed. Smile | :)
GeneralMy vote of 5 Pin
Volynsky Alex9-Dec-14 19:15
professionalVolynsky Alex9-Dec-14 19:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.