Slide 1

Slide 1 text

ANIMATING CUSTOM LAYER PROPERTIES How hard could it be?

Slide 2

Slide 2 text

The Dream

Slide 3

Slide 3 text

The Dream

Slide 4

Slide 4 text

Layer @interface CircleLayer : CALayer @property CGFloat radius; @end @implementation CircleLayer - (void)drawInContext:(CGContextRef)ctx { CGContextSetFillColorWithColor(ctx, [[UIColor redColor] CGColor]); CGFloat radius = self.radius; CGRect rect; rect.size = CGSizeMake(radius, radius); rect.origin.x = (self.bounds.size.width - radius) / 2; rect.origin.y = (self.bounds.size.height - radius) / 2; CGContextAddEllipseInRect(ctx, rect); CGContextFillPath(ctx); } @end

Slide 5

Slide 5 text

ViewController - (void)viewDidLoad { CircleLayer *circleLayer = [CircleLayer new]; circleLayer.radius = 50; circleLayer.frame = self.view.bounds; [self.view.layer addSublayer:circleLayer]; UIGestureRecognizer *g = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)]; [self.view addGestureRecognizer:g]; } - (void)tap:(UIGestureRecognizer *)recognizer { [CATransaction setAnimationDuration:2]; CircleLayer *circleLayer = (CircleLayer*)[self.view.layer.sublayers objectAtIndex:0]; [circleLayer setRadius:100.0]; }

Slide 6

Slide 6 text

!@#$

Slide 7

Slide 7 text

CALayers Hate to Draw • Layers don’t display just because they need to • Not even the first time • Not even when you change stuff... normally. - (CircleLayer *)init { self = [super init]; if (self) { [self setNeedsDisplay]; } return self; }

Slide 8

Slide 8 text

Sweet

Slide 9

Slide 9 text

Sweet Tap ...

Slide 10

Slide 10 text

Sweet Tap ... Tap

Slide 11

Slide 11 text

Sweet Tap ... Tap Tap

Slide 12

Slide 12 text

Sweet Tap ... Tap Tap Tap

Slide 13

Slide 13 text

Sweet Tap ... Tap Tap Tap Tap Tap Tap Tap Tap Tap Tap Tap Tap Dang it!

Slide 14

Slide 14 text

CALayers Hate to Draw

Slide 15

Slide 15 text

CALayers Hate to Draw • Actually, it just doesn’t know it needs to

Slide 16

Slide 16 text

CALayers Hate to Draw • Actually, it just doesn’t know it needs to @implementation CircleLayer @dynamic radius; + (BOOL)needsDisplayForKey:key:(NSString *)key { if ([key isEqualToString:@"radius"]) { return YES; } return [super needsDisplayForKey:key]; }

Slide 17

Slide 17 text

CALayers Hate to Draw • Actually, it just doesn’t know it needs to @implementation CircleLayer @dynamic radius; + (BOOL)needsDisplayForKey:key:(NSString *)key { if ([key isEqualToString:@"radius"]) { return YES; } return [super needsDisplayForKey:key]; } Hey, what’s that?

Slide 18

Slide 18 text

@dynamic? • CALayer auto-generates its own accessors • It does not play well with ivars (like from @synthesize) • You will forget. It will drive you crazy. Try to remember.

Slide 19

Slide 19 text

We’re good now, right?

Slide 20

Slide 20 text

We’re good now, right?

Slide 21

Slide 21 text

Cross-fade?!?!

Slide 22

Slide 22 text

THE MEAT Actions

Slide 23

Slide 23 text

Magic • Ever wonder how those implicit layer animations work?

Slide 24

Slide 24 text

Magic • Ever wonder how those implicit layer animations work? • Start a transaction

Slide 25

Slide 25 text

Magic • Ever wonder how those implicit layer animations work? • Start a transaction • Assign something (setValue:forKey:)

Slide 26

Slide 26 text

Magic • Ever wonder how those implicit layer animations work? • Start a transaction • Assign something (setValue:forKey:) • Look for automagical actions!

Slide 27

Slide 27 text

Magic • Ever wonder how those implicit layer animations work? • Start a transaction • Assign something (setValue:forKey:) • Look for automagical actions! • Commit transaction

Slide 28

Slide 28 text

Magic • Ever wonder how those implicit layer animations work? • Start a transaction • Assign something (setValue:forKey:) • Look for automagical actions! • Commit transaction • Apply all those automagical actions...

Slide 29

Slide 29 text

Actions - (id < CAAction >)actionForKey:(NSString *)key { if ([self presentationLayer] != nil) { if ([key isEqualToString:@"radius"]) { CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"radius"]; anim.fromValue = [[self presentationLayer] valueForKey:@"radius"]; return anim; } } return [super actionForKey:key]; }

Slide 30

Slide 30 text

Actions - (id < CAAction >)actionForKey:(NSString *)key { if ([self presentationLayer] != nil) { if ([key isEqualToString:@"radius"]) { CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"radius"]; anim.fromValue = [[self presentationLayer] valueForKey:@"radius"]; return anim; } } return [super actionForKey:key]; } Hey, what’s that?

Slide 31

Slide 31 text

Copy (initWithLayer:) Copy (initWithLayer:) Presentation Model Render

Slide 32

Slide 32 text

Copy (initWithLayer:) Copy (initWithLayer:) Presentation Model Render

Slide 33

Slide 33 text

The Hall of (Almost) Mirrors

Slide 34

Slide 34 text

The Hall of (Almost) Mirrors • Presentation layer copies are created with initWithLayer:

Slide 35

Slide 35 text

The Hall of (Almost) Mirrors • Presentation layer copies are created with initWithLayer: • You need to copy any non-@dynamic data here

Slide 36

Slide 36 text

The Hall of (Almost) Mirrors • Presentation layer copies are created with initWithLayer: • You need to copy any non-@dynamic data here • Who needs non-@dynamic data in a CALayer? Beats me.

Slide 37

Slide 37 text

The Hall of (Almost) Mirrors • Presentation layer copies are created with initWithLayer: • You need to copy any non-@dynamic data here • Who needs non-@dynamic data in a CALayer? Beats me. • Just use @dynamic. Fewer bugs. (Subject to change.)

Slide 38

Slide 38 text

Bringing It All Together • setNeedsDisplay: • needsDisplayForKey: • @dynamic properties • actionForKey:

Slide 39

Slide 39 text

No content

Slide 40

Slide 40 text

Living the dream

Slide 41

Slide 41 text

QUESTIONS?

Slide 42

Slide 42 text

New Dream (That Was Quick)

Slide 43

Slide 43 text

New Dream (That Was Quick) • CA can animate lots of stuff, including CGColor

Slide 44

Slide 44 text

New Dream (That Was Quick) • CA can animate lots of stuff, including CGColor • But not UIColor :(

Slide 45

Slide 45 text

New Dream (That Was Quick) • CA can animate lots of stuff, including CGColor • But not UIColor :( • I’m so lazy.... I want it to animate UIColor for me

Slide 46

Slide 46 text

New Dream (That Was Quick) • CA can animate lots of stuff, including CGColor • But not UIColor :( • I’m so lazy.... I want it to animate UIColor for me • Yeah, pretty lazy.... but a good example.

Slide 47

Slide 47 text

The Boring Parts @interface CircleLayer : CALayer @property CGFloat radius; @property UIColor *color; @end @dynamic color; - (void)setColorRef:(CGColorRef)colorRef { self.color = [UIColor colorWithCGColor:colorRef]; } - (CGColorRef)colorRef { return self.color.CGColor; } ...Update needsDisplayForKey, init, drawInContext:... @interface CircleLayer () @property CGColorRef colorRef; @end

Slide 48

Slide 48 text

The Redirecting Action - (id < CAAction >)actionForKey:(NSString *)key { ... if ([key isEqualToString:@"color"]) { CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"colorRef"]; anim.fromValue = [[self presentationLayer] valueForKey:@"colorRef"]; return anim; } ... }

Slide 49

Slide 49 text

The Redirecting Action - (id < CAAction >)actionForKey:(NSString *)key { ... if ([key isEqualToString:@"color"]) { CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"colorRef"]; anim.fromValue = [[self presentationLayer] valueForKey:@"colorRef"]; return anim; } ... } Who said we have to animate ourselves?

Slide 50

Slide 50 text

Let’s Do This - (void)tap:(UIGestureRecognizer *)recognizer { [CATransaction setAnimationDuration:2]; CircleLayer *circleLayer = (CircleLayer*)[self.view.layer.sublayers objectAtIndex:0]; [circleLayer setRadius:100.0]; [circleLayer setColor:[UIColor greenColor]]; // Change in flight! double delayInSeconds = 1.0; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ [CATransaction setAnimationDuration:1]; [circleLayer setPosition:CGPointZero]; [circleLayer setRadius:25.0]; }); }

Slide 51

Slide 51 text

No content

Slide 52

Slide 52 text

Q&A

Slide 53

Slide 53 text

robnapier.net