Slide 1

Slide 1 text

to drawRect or not to drawRect?

Slide 2

Slide 2 text

@iwantmyrealname Sam Davies

Slide 3

Slide 3 text

leanpub.com/ios7daybyday

Slide 4

Slide 4 text

@shinobicontrols

Slide 5

Slide 5 text

ShinobiControls shinobicontrols.com/giveaway

Slide 6

Slide 6 text

what do i mean by drawing?

Slide 7

Slide 7 text

Drawing Checklist Shapes Gradients Text Lorem Textures Animation

Slide 8

Slide 8 text

how do i draw on iOS?

Slide 9

Slide 9 text

Drawing on iOS UIView CoreGraphics CoreAnimation OpenGLES

Slide 10

Slide 10 text

Drawing on iOS UIView CoreGraphics CoreAnimation OpenGLES

Slide 11

Slide 11 text

How do the bits fit together? UIView Hierarchy

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

I want to draw some

Slide 18

Slide 18 text

simplistic approach with UIImageView additional configurability with CoreGraphics efficient animation with CoreAnimation

Slide 19

Slide 19 text

SCArrowView protocol @protocol SCArrowView 
 
 - (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

Slide 20

Slide 20 text

UIImageView

Slide 21

Slide 21 text

˒UIView = blank canvas ˒Various subclasses for creating content ˒UIImageView displays images ˒Highly optimised for this purpose UIImageView

Slide 22

Slide 22 text

UIImageView Arrow if(! self.imageView && self.image) {
 self.imageView = [[UIImageView alloc] initWithImage:self.image];
 [self addSubView:self.imageView];
 } from to

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

UIImageView Arrow from to CGFloat arrowAngle = atan2((self.to.y - self.from.y),
 (self.to.x - self.from.x));
 self.arrowImageView.transform =
 CGAffineTransformMakeRotation(arrowAngle);

Slide 26

Slide 26 text

UIImageView Arrow from to UIImage *preColoredArrow = [self preColoredArrowImage];
 self.tintColor = self.color;
 self.image = [preColoredArrow
 imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
 self.arrowImageView.image = self.image;

Slide 27

Slide 27 text

demonstration

Slide 28

Slide 28 text

UIImageView Arrow

Slide 29

Slide 29 text

UIImageView Arrow

Slide 30

Slide 30 text

UIImageView Arrow

Slide 31

Slide 31 text

UIImageView Arrow

Slide 32

Slide 32 text

UIImageView Arrow

Slide 33

Slide 33 text

Drawing Checklist Shapes Gradients Text Lorem Textures Animation

Slide 34

Slide 34 text

Drawing Checklist Shapes Gradients Text Lorem Textures Animation

Slide 35

Slide 35 text

CoreGraphics

Slide 36

Slide 36 text

CoreGraphics

Slide 37

Slide 37 text

CoreGraphics

Slide 38

Slide 38 text

CoreGraphics

Slide 39

Slide 39 text

CoreGraphics

Slide 40

Slide 40 text

CoreGraphics

Slide 41

Slide 41 text

CGContextAddLineToPoint ! CGContextAddArc
 CGContextAddArcToPoint ! CGContextAddCurve
 CGContextAddQuadCurve Paths

Slide 42

Slide 42 text

- addLineToPoint: ! + bezierPathWithOvalInRect:
 - addArcWithCenter:radius:startAngle:
 endAngle:
 clockwise: ! - addCurveToPoint:controlPoint1:
 controlPoint2:
 - addQuadCurveToPoint:controlPoint: UIBezierPath

Slide 43

Slide 43 text

CoreGraphics Functionality Patterns Shadows Gradients Masks

Slide 44

Slide 44 text

drawRect ˒Presets the CGContext for you ˒UIGraphicsGetCurrentContext() ˒Paint over the top of the pre-rendered view

Slide 45

Slide 45 text

CoreGraphics Arrow CGContextRef cxt = UIGraphicsGetCurrentContext();

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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();

Slide 49

Slide 49 text

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();

Slide 50

Slide 50 text

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();

Slide 51

Slide 51 text

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();

Slide 52

Slide 52 text

demonstration

Slide 53

Slide 53 text

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];
 }

Slide 54

Slide 54 text

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];
 }

Slide 55

Slide 55 text

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];
 }

Slide 56

Slide 56 text

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];
 }

Slide 57

Slide 57 text

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];
 }

Slide 58

Slide 58 text

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];
 }

Slide 59

Slide 59 text

demonstration

Slide 60

Slide 60 text

Drawing Checklist Shapes Gradients Text Lorem Textures Animation

Slide 61

Slide 61 text

Drawing Checklist Shapes Gradients Text Lorem Textures Animation

Slide 62

Slide 62 text

CoreAnimation

Slide 63

Slide 63 text

Presentation Tree Render Tree Application Framework ˒ CALayer is the fundamental object ˒ Exists in a hierarchy ˒ 3 distinct hierarchies Model Tree CoreAnimation

Slide 64

Slide 64 text

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…

Slide 65

Slide 65 text

Providing Content to CALayer ˒ contents property ˒ drawLayer:inContext: method on CALayerDelegate ˒ Subclass and override drawing methods - drawInContext: or display ˒ Specialist subclasses of CALayer

Slide 66

Slide 66 text

CALayer Subclasses Shape Gradient EAGL Replicator Scroll Text Tiled … Lorem

Slide 67

Slide 67 text

Construction of CAArrow 2 CAShapeLayers UIView:layer (CALayer)

Slide 68

Slide 68 text

Construction of CAArrow 2 CAShapeLayers UIView:layer (CALayer)

Slide 69

Slide 69 text

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;

Slide 70

Slide 70 text

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;

Slide 71

Slide 71 text

demonstration

Slide 72

Slide 72 text

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 …

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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"];

Slide 80

Slide 80 text

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"];

Slide 81

Slide 81 text

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"];

Slide 82

Slide 82 text

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"];

Slide 83

Slide 83 text

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"];

Slide 84

Slide 84 text

demonstration

Slide 85

Slide 85 text

Drawing Checklist Shapes Gradients Text Lorem Textures Animation

Slide 86

Slide 86 text

Drawing Checklist Shapes Gradients Text Lorem Textures Animation

Slide 87

Slide 87 text

Animation

Slide 88

Slide 88 text

Performance CoreAnimation CoreGraphics

Slide 89

Slide 89 text

Performance CoreGraphics CoreAnimation UIImageView cpu time fps ❌ ❌

Slide 90

Slide 90 text

˒ 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

Slide 91

Slide 91 text

learn by building thanks github.com/sammyd/iOS-ArrowDrawing ! @iwantmyrealname iwantmyreal.name