Save 37% off PRO during our Black Friday Sale! »

To drawRect or not to drawRect?

Ddd6d3bac7772fa67fc5e312a18bdaec?s=47 sammyd
March 29, 2014

To drawRect or not to drawRect?

All too often online advice for constructing beautiful UI on iOS starts out by suggesting that you create a custom UIView subclass and override drawRect, but is this the best approach? What alternatives exist? How should I choose between them.

This talk will involve a high-level look at the different graphical processing options on iOS, including CoreGraphics, CoreAnimation, UIView and OpenGL. Then we'll take a look at the following questions, with plenty of examples: - what does drawRect actually do? - when is drawRect slow? - what are all these CALayer subclasses? - when should I be choosing each of the options?

By the end of the talk we'll have covered some specific examples of how to choose each of the drawing technologies, and will hopefully have persuaded you that drawRect isn't always the best choice.

Ddd6d3bac7772fa67fc5e312a18bdaec?s=128

sammyd

March 29, 2014
Tweet

Transcript

  1. to drawRect or not to drawRect?

  2. @iwantmyrealname Sam Davies

  3. leanpub.com/ios7daybyday

  4. @shinobicontrols

  5. ShinobiControls shinobicontrols.com/giveaway

  6. what do i mean by drawing?

  7. Drawing Checklist Shapes Gradients Text Lorem Textures Animation

  8. how do i draw on iOS?

  9. Drawing on iOS UIView CoreGraphics CoreAnimation OpenGLES

  10. Drawing on iOS UIView CoreGraphics CoreAnimation OpenGLES

  11. How do the bits fit together? UIView Hierarchy

  12. How do the bits fit together? UIView Hierarchy Graphics Hardware

    via !
  13. How do the bits fit together? UIView Hierarchy Graphics Hardware

    via ! CALayer rotate 30° opacity 60% backed by
  14. How do the bits fit together? UIView Hierarchy Graphics Hardware

    via ! CALayer rotate 30° opacity 60% backed by
  15. How do the bits fit together? UIView Hierarchy Graphics Hardware

    via ! CALayer rotate 30° opacity 60% backed by drawRect:
  16. So what actually is drawRect? ˒ UIView backed by CALayer

    ˒ Implementation of CALayerDelegate method drawLayer:inContext: ˒ Informal protocol ˒ Use to provide layer content and arrange sublayers
  17. I want to draw some

  18. simplistic approach with UIImageView additional configurability with CoreGraphics efficient animation

    with CoreAnimation
  19. SCArrowView protocol @protocol SCArrowView <NSObject>
 
 - (instancetype)initWithFrame:(CGRect)frame
 from:(CGPoint)from
 to:(CGPoint)to;

    @property (nonatomic, assign) CGPoint from;
 @property (nonatomic, assign) CGPoint to;
 @property (nonatomic, assign) CGFloat headSize;
 @property (nonatomic, assign) CGFloat lineThickness;
 @property (nonatomic, assign) CGFloat bendiness;
 @property (nonatomic, strong) UIColor *color; - (void)redrawArrow; @end
  20. UIImageView

  21. ˒UIView = blank canvas ˒Various subclasses for creating content ˒UIImageView

    displays images ˒Highly optimised for this purpose UIImageView
  22. UIImageView Arrow if(! self.imageView && self.image) {
 self.imageView = [[UIImageView

    alloc] initWithImage:self.image];
 [self addSubView:self.imageView];
 } from to
  23. UIImageView Arrow CGPoint ivCentre = CGPointZero;
 ivCentre.x = (self.from.x +

    self.to.x) / 2.0;
 ivCentre.y = (self.from.y + self.to.y) / 2.0;
 self.imageView.center = ivCentre; from to
  24. UIImageView Arrow CGFloat arrowLength = sqrt(pow((self.to.y - self.from.y),2) +
 pow((self.to.x

    - self.from.x),2) );
 CGRect arrowBounds = self.imageView.bounds;
 arrowBounds.size.width = arrowLength;
 self.imageView.bounds = arrowBounds; from to
  25. UIImageView Arrow from to CGFloat arrowAngle = atan2((self.to.y - self.from.y),


    (self.to.x - self.from.x));
 self.arrowImageView.transform =
 CGAffineTransformMakeRotation(arrowAngle);
  26. UIImageView Arrow from to UIImage *preColoredArrow = [self preColoredArrowImage];
 self.tintColor

    = self.color;
 self.image = [preColoredArrow
 imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
 self.arrowImageView.image = self.image;
  27. demonstration

  28. UIImageView Arrow

  29. UIImageView Arrow

  30. UIImageView Arrow

  31. UIImageView Arrow

  32. UIImageView Arrow

  33. Drawing Checklist Shapes Gradients Text Lorem Textures Animation

  34. Drawing Checklist Shapes Gradients Text Lorem Textures Animation

  35. CoreGraphics

  36. CoreGraphics

  37. CoreGraphics

  38. CoreGraphics

  39. CoreGraphics

  40. CoreGraphics

  41. CGContextAddLineToPoint ! CGContextAddArc
 CGContextAddArcToPoint ! CGContextAddCurve
 CGContextAddQuadCurve Paths

  42. - addLineToPoint: ! + bezierPathWithOvalInRect:
 - addArcWithCenter:radius:startAngle:
 endAngle:
 clockwise: !

    - addCurveToPoint:controlPoint1:
 controlPoint2:
 - addQuadCurveToPoint:controlPoint: UIBezierPath
  43. CoreGraphics Functionality Patterns Shadows Gradients Masks

  44. drawRect ˒Presets the CGContext for you ˒UIGraphicsGetCurrentContext() ˒Paint over the

    top of the pre-rendered view
  45. CoreGraphics Arrow CGContextRef cxt = UIGraphicsGetCurrentContext();

  46. CoreGraphics Arrow CGContextMoveToPoint(cxt, start.x, start.y); CGContextRef cxt = UIGraphicsGetCurrentContext();

  47. CoreGraphics Arrow CGContextMoveToPoint(cxt, start.x, start.y); CGContextAddQuadCurveToPoint(cxt, control.x, control.y, end.x, end.y);

    CGContextRef cxt = UIGraphicsGetCurrentContext();
  48. CoreGraphics Arrow CGContextMoveToPoint(cxt, start.x, start.y); CGContextAddQuadCurveToPoint(cxt, control.x, control.y, end.x, end.y);

    CGContextMoveToPoint(cxt, arrowSide1.x, arrowSide1.y); CGContextRef cxt = UIGraphicsGetCurrentContext();
  49. CoreGraphics Arrow CGContextMoveToPoint(cxt, start.x, start.y); CGContextAddQuadCurveToPoint(cxt, control.x, control.y, end.x, end.y);

    CGContextMoveToPoint(cxt, arrowSide1.x, arrowSide1.y); CGContextAddLineToPoint(cxt, end.x, end.y); CGContextRef cxt = UIGraphicsGetCurrentContext();
  50. CoreGraphics Arrow CGContextMoveToPoint(cxt, start.x, start.y); CGContextAddQuadCurveToPoint(cxt, control.x, control.y, end.x, end.y);

    CGContextMoveToPoint(cxt, arrowSide1.x, arrowSide1.y); CGContextAddLineToPoint(cxt, arrowSide2.x, arrowSide2.y); CGContextAddLineToPoint(cxt, end.x, end.y); CGContextRef cxt = UIGraphicsGetCurrentContext();
  51. CoreGraphics Arrow CGContextMoveToPoint(cxt, start.x, start.y); CGContextAddQuadCurveToPoint(cxt, control.x, control.y, end.x, end.y);

    CGContextMoveToPoint(cxt, arrowSide1.x, arrowSide1.y); CGContextAddLineToPoint(cxt, arrowSide2.x, arrowSide2.y); CGContextAddLineToPoint(cxt, end.x, end.y); CGContextStrokePath(cxt); CGContextRef cxt = UIGraphicsGetCurrentContext();
  52. demonstration

  53. Adding animation - (void)animationUpdate:(CADisplayLink *)sender
 {
 self.animating = YES
 if(!self.timer)

    {
 self.timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(animationUpdate:)];
 [self.timer addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
 self.animationStartTime = CACurrentMediaTime();
 } 
 CGFloat propComplete = (CACurrentMediaTime() - self.animationStartTime) / self.animationTime;
 if(propComplete >= 1) {
 self.currentEnd = self.to;
 [self.timer invalidate];
 self.timer = nil;
 } else {
 self.currentEnd = CGPointMake((self.to.x - self.from.x) * propComplete + self.from.x,
 (self.to.y - self.from.y) * propComplete + self.from.y);
 } 
 [self setNeedsDisplay];
 }
  54. Adding animation - (void)animationUpdate:(CADisplayLink *)sender
 {
 self.animating = YES
 if(!self.timer)

    {
 self.timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(animationUpdate:)];
 [self.timer addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
 self.animationStartTime = CACurrentMediaTime();
 } 
 CGFloat propComplete = (CACurrentMediaTime() - self.animationStartTime) / self.animationTime;
 if(propComplete >= 1) {
 self.currentEnd = self.to;
 [self.timer invalidate];
 self.timer = nil;
 } else {
 self.currentEnd = CGPointMake((self.to.x - self.from.x) * propComplete + self.from.x,
 (self.to.y - self.from.y) * propComplete + self.from.y);
 } 
 [self setNeedsDisplay];
 }
  55. Adding animation - (void)animationUpdate:(CADisplayLink *)sender
 {
 self.animating = YES
 if(!self.timer)

    {
 self.timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(animationUpdate:)];
 [self.timer addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
 self.animationStartTime = CACurrentMediaTime();
 } 
 CGFloat propComplete = (CACurrentMediaTime() - self.animationStartTime) / self.animationTime;
 if(propComplete >= 1) {
 self.currentEnd = self.to;
 [self.timer invalidate];
 self.timer = nil;
 } else {
 self.currentEnd = CGPointMake((self.to.x - self.from.x) * propComplete + self.from.x,
 (self.to.y - self.from.y) * propComplete + self.from.y);
 } 
 [self setNeedsDisplay];
 }
  56. Adding animation - (void)animationUpdate:(CADisplayLink *)sender
 {
 self.animating = YES
 if(!self.timer)

    {
 self.timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(animationUpdate:)];
 [self.timer addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
 self.animationStartTime = CACurrentMediaTime();
 } 
 CGFloat propComplete = (CACurrentMediaTime() - self.animationStartTime) / self.animationTime;
 if(propComplete >= 1) {
 self.currentEnd = self.to;
 [self.timer invalidate];
 self.timer = nil;
 } else {
 self.currentEnd = CGPointMake((self.to.x - self.from.x) * propComplete + self.from.x,
 (self.to.y - self.from.y) * propComplete + self.from.y);
 } 
 [self setNeedsDisplay];
 }
  57. Adding animation - (void)animationUpdate:(CADisplayLink *)sender
 {
 self.animating = YES
 if(!self.timer)

    {
 self.timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(animationUpdate:)];
 [self.timer addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
 self.animationStartTime = CACurrentMediaTime();
 } 
 CGFloat propComplete = (CACurrentMediaTime() - self.animationStartTime) / self.animationTime;
 if(propComplete >= 1) {
 self.currentEnd = self.to;
 [self.timer invalidate];
 self.timer = nil;
 } else {
 self.currentEnd = CGPointMake((self.to.x - self.from.x) * propComplete + self.from.x,
 (self.to.y - self.from.y) * propComplete + self.from.y);
 } 
 [self setNeedsDisplay];
 }
  58. Adding animation - (void)animationUpdate:(CADisplayLink *)sender
 {
 self.animating = YES
 if(!self.timer)

    {
 self.timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(animationUpdate:)];
 [self.timer addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
 self.animationStartTime = CACurrentMediaTime();
 } 
 CGFloat propComplete = (CACurrentMediaTime() - self.animationStartTime) / self.animationTime;
 if(propComplete >= 1) {
 self.currentEnd = self.to;
 [self.timer invalidate];
 self.timer = nil;
 } else {
 self.currentEnd = CGPointMake((self.to.x - self.from.x) * propComplete + self.from.x,
 (self.to.y - self.from.y) * propComplete + self.from.y);
 } 
 [self setNeedsDisplay];
 }
  59. demonstration

  60. Drawing Checklist Shapes Gradients Text Lorem Textures Animation

  61. Drawing Checklist Shapes Gradients Text Lorem Textures Animation

  62. CoreAnimation

  63. Presentation Tree Render Tree Application Framework ˒ CALayer is the

    fundamental object ˒ Exists in a hierarchy ˒ 3 distinct hierarchies Model Tree CoreAnimation
  64. CALayer self.view.layer.cornerRadius = 5.0; self.view.layer.borderColor = [UIColor grayColor].CGColor; self.view.layer.borderWidth =

    2; We’ve all used CALayer before… …usually to make this… into this…
  65. Providing Content to CALayer ˒ contents property ˒ drawLayer:inContext: method

    on CALayerDelegate ˒ Subclass and override drawing methods - drawInContext: or display ˒ Specialist subclasses of CALayer
  66. CALayer Subclasses Shape Gradient EAGL Replicator Scroll Text Tiled …

    Lorem
  67. Construction of CAArrow 2 CAShapeLayers UIView:layer (CALayer)

  68. Construction of CAArrow 2 CAShapeLayers UIView:layer (CALayer)

  69. Creating Arrow Path self.arrowPathLayer = [CAShapeLayer layer];
 [self.layer addSublayer:self.arrowPathLayer]; self.arrowPathLayer.bounds

    = self.bounds;
 self.arrowPathLayer.position = CGPointMake(CGRectGetMidX(self.bounds),
 CGRectGetMidY(self.bounds)); self.arrowPathLayer.strokeColor = self.color.CGColor;
 self.arrowPathLayer.lineWidth = self.lineThickness; UIBezierPath *path = [self.arrowPath arrowBezierPath];
 self.arrowPathLayer.path = path.CGPath;
  70. Creating Arrow Head self.arrowHeadLayer = [CAShapeLayer layer];
 [self.layer addSublayer:self.arrowHeadLayer]; self.arrowHeadLayer.bounds

    = self.bounds;
 self.arrowHeadLayer.position = CGPointMake(CGRectGetMidX(self.bounds),
 CGRectGetMidY(self.bounds)); self.arrowHeadLayer.strokeColor = self.color.CGColor;
 self.arrowHeadLayer.lineWidth = self.lineThickness; UIBezierPath *headPath = [self arrowHeadBezierPath]; self.arrowHeadLayer.path = headPath.CGPath;
  71. demonstration

  72. CAAnimation CALayer Implicit ! backgroundColor borderColor borderWidth bounds cornerRadius mask

    opacity position transform … CAGradientLayer ! colors locations startPoint endPoint CAShapeLayer ! lineDashPhase fillColor path* strokeStart strokeEnd … CATextLayer ! fontSize* foregroundColor* CAReplicatorLayer ! instanceAlphaOffset instanceColor instanceDelay …
  73. Stroke Animation 0 1 CABasicAnimation *animation = [CABasicAnimation
 animationWithKeyPath:@"strokeEnd"];
 animation.duration

    = 2;
 animation.fromValue = @0;
 animation.toValue = @1;
 animation.timingFunction = [CAMediaTimingFunction
 functionWithName:kCAMediaTimingFunctionEaseIn];
  74. Stroke Animation 0 1 CABasicAnimation *animation = [CABasicAnimation
 animationWithKeyPath:@"strokeEnd"];
 animation.duration

    = 2;
 animation.fromValue = @0;
 animation.toValue = @1;
 animation.timingFunction = [CAMediaTimingFunction
 functionWithName:kCAMediaTimingFunctionEaseIn];
  75. Arrow Head Animation CABasicAnimation *headAnimationLeft = [CABasicAnimation
 animationWithKeyPath:@"strokeEnd"];
 headAnimationLeft.duration =

    1.0;
 headAnimationLeft.fromValue = @0.5;
 headAnimationLeft.toValue = @1;
 headAnimationLeft.beginTime = 2;
 headAnimationLeft.timingFunction = [CAMediaTimingFunction
 functionWithName:kCAMediaTimingFunctionEaseOut]; 1 0.5 0 1 0.5 0
  76. Arrow Head Animation CABasicAnimation *headAnimationLeft = [CABasicAnimation
 animationWithKeyPath:@"strokeEnd"];
 headAnimationLeft.duration =

    1.0;
 headAnimationLeft.fromValue = @0.5;
 headAnimationLeft.toValue = @1;
 headAnimationLeft.beginTime = 2;
 headAnimationLeft.timingFunction = [CAMediaTimingFunction
 functionWithName:kCAMediaTimingFunctionEaseOut]; 1 0.5 0 1 0.5 0
  77. Arrow Head Animation CABasicAnimation *headAnimationLeft = [CABasicAnimation
 animationWithKeyPath:@"strokeEnd"];
 headAnimationLeft.duration =

    1.0;
 headAnimationLeft.fromValue = @0.5;
 headAnimationLeft.toValue = @1;
 headAnimationLeft.beginTime = 2;
 headAnimationLeft.timingFunction = [CAMediaTimingFunction
 functionWithName:kCAMediaTimingFunctionEaseOut]; CABasicAnimation *headAnimationRight = [CABasicAnimation
 animationWithKeyPath:@"strokeStart"];
 headAnimationRight.duration = 1.0;
 headAnimationRight.fromValue = @0.5;
 headAnimationRight.toValue = @0;
 headAnimationRight.beginTime = 2;
 headAnimationRight.timingFunction = [CAMediaTimingFunction
 functionWithName:kCAMediaTimingFunctionEaseOut]; 1 0.5 0 1 0.5 0
  78. Arrow Head Animation CABasicAnimation *headAnimationLeft = [CABasicAnimation
 animationWithKeyPath:@"strokeEnd"];
 headAnimationLeft.duration =

    1.0;
 headAnimationLeft.fromValue = @0.5;
 headAnimationLeft.toValue = @1;
 headAnimationLeft.beginTime = 2;
 headAnimationLeft.timingFunction = [CAMediaTimingFunction
 functionWithName:kCAMediaTimingFunctionEaseOut]; CABasicAnimation *headAnimationRight = [CABasicAnimation
 animationWithKeyPath:@"strokeStart"];
 headAnimationRight.duration = 1.0;
 headAnimationRight.fromValue = @0.5;
 headAnimationRight.toValue = @0;
 headAnimationRight.beginTime = 2;
 headAnimationRight.timingFunction = [CAMediaTimingFunction
 functionWithName:kCAMediaTimingFunctionEaseOut]; 1 0.5 0 1 0.5 0
  79. Arrow Head Animation CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
 animationGroup.duration =

    3;
 animationGroup.animations = @[headHidden, headAnimationRight,
 headAnimationLeft]; [self.arrowPathLayer addAnimation:animation
 forKey:@"SCArrowDrawAnimation"];
 [self.arrowHeadLayer addAnimation:animationGroup
 forKey:@"SCArrowDrawHeadAnimation"];
  80. Arrow Head Animation CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
 animationGroup.duration =

    3;
 animationGroup.animations = @[headHidden, headAnimationRight,
 headAnimationLeft]; [self.arrowPathLayer addAnimation:animation
 forKey:@"SCArrowDrawAnimation"];
 [self.arrowHeadLayer addAnimation:animationGroup
 forKey:@"SCArrowDrawHeadAnimation"];
  81. Arrow Head Animation CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
 animationGroup.duration =

    3;
 animationGroup.animations = @[headHidden, headAnimationRight,
 headAnimationLeft]; [self.arrowPathLayer addAnimation:animation
 forKey:@"SCArrowDrawAnimation"];
 [self.arrowHeadLayer addAnimation:animationGroup
 forKey:@"SCArrowDrawHeadAnimation"];
  82. Arrow Head Animation CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
 animationGroup.duration =

    3;
 animationGroup.animations = @[headHidden, headAnimationRight,
 headAnimationLeft]; [self.arrowPathLayer addAnimation:animation
 forKey:@"SCArrowDrawAnimation"];
 [self.arrowHeadLayer addAnimation:animationGroup
 forKey:@"SCArrowDrawHeadAnimation"];
  83. Arrow Head Animation CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
 animationGroup.duration =

    3;
 animationGroup.animations = @[headHidden, headAnimationRight,
 headAnimationLeft]; [self.arrowPathLayer addAnimation:animation
 forKey:@"SCArrowDrawAnimation"];
 [self.arrowHeadLayer addAnimation:animationGroup
 forKey:@"SCArrowDrawHeadAnimation"];
  84. demonstration

  85. Drawing Checklist Shapes Gradients Text Lorem Textures Animation

  86. Drawing Checklist Shapes Gradients Text Lorem Textures Animation

  87. Animation

  88. Performance CoreAnimation CoreGraphics

  89. Performance CoreGraphics CoreAnimation UIImageView cpu time fps ❌ ❌

  90. ˒ UIImageView is mega simple ˒ Use UIBezierPath to draw

    shapes ˒ Don’t try and animate with drawRect ˒ Discover the CALayer subclasses ˒ CALayer offers software design advantages ˒ CALayer when interaction not required in summary
  91. learn by building thanks github.com/sammyd/iOS-ArrowDrawing ! @iwantmyrealname iwantmyreal.name