Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Animating Custom Layer Properties by Rob Napier

Animating Custom Layer Properties by Rob Napier

Slides from Rob Napier's (@cocoaphony) talk on Animating Custom Layer Properties with Core Animation at CocoaHeads June 2012 in Raleigh, NC

Triangle Cocoa

June 30, 2012
Tweet

More Decks by Triangle Cocoa

Other Decks in Programming

Transcript

  1. 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
  2. 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]; }
  3. 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; }
  4. Sweet Tap ... Tap Tap Tap Tap Tap Tap Tap

    Tap Tap Tap Tap Tap Dang it!
  5. 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]; }
  6. 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?
  7. @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.
  8. Magic • Ever wonder how those implicit layer animations work?

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

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

    • Start a transaction • Assign something (setValue:forKey:) • Look for automagical actions! • Commit transaction
  11. 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...
  12. 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]; }
  13. 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?
  14. The Hall of (Almost) Mirrors • Presentation layer copies are

    created with initWithLayer: • You need to copy any non-@dynamic data here
  15. 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.
  16. 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.)
  17. New Dream (That Was Quick) • CA can animate lots

    of stuff, including CGColor • But not UIColor :(
  18. 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
  19. 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.
  20. 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
  21. 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; } ... }
  22. 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?
  23. 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]; }); }
  24. Q&A