Slide 1

Slide 1 text

BRING YOUR APP TO LIFE WITH CALAYERS CALAYERS, PAINTCODE, AND ANIMATIONS STEPHEN BARNES - @SMBARNE - FITBIT

Slide 2

Slide 2 text

WHO IS THIS GUY? HELLO, I'M STEPHEN. SMBARNE ENGINEERINGART.IO

Slide 3

Slide 3 text

HTTP://BIT.LY/CALAYERTALK

Slide 4

Slide 4 text

IT ALL STARTED WHEN...

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

SO HOW DO WE BUILD THIS?

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

SKETCH

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

PAINTCODE

Slide 12

Slide 12 text

PROS > Vector output > Performant > Easily adjustable via WYSIWYG Editor > Anchor Points, Relative Location, Transforms > Outputs standard iOS code

Slide 13

Slide 13 text

CONS > Great for controls, requires alterations for sequential animation > Relies on drawRect > Imposes a third party tool for UI configuration > Possible compile time increases

Slide 14

Slide 14 text

GROUPING

Slide 15

Slide 15 text

GENERATED CODE - (void)drawIntroScreenWithFrame: (CGRect)frame rightArmAngle: (CGFloat)rightArmAngle { //// General Declarations CGContextRef context = UIGraphicsGetCurrentContext(); ... //// pictureFrameGroup { //// pictureBackground Drawing UIBezierPath* pictureBackgroundPath = [UIBezierPath bezierPathWithRect:CGRectMake( CGRectGetMinX(pictureFrameGroup) + 3.27, CGRectGetMinY(pictureFrameGroup) + 1.48, 122.75, 94.65)]; [pictureBackgroundBlue setFill]; [pictureBackgroundPath fill]; ...

Slide 16

Slide 16 text

CALAYERS

Slide 17

Slide 17 text

VECTOR CLEANUP > Delete unused shapes > Merge contiguous shapes with same color > Name colors > Name layers

Slide 18

Slide 18 text

BREAKDOWN AND TRANSFORM GENERATED CODE > Custom View with custom CALayer > Custom CALayer exposes animatable groups > Manipulate Paintcode output

Slide 19

Slide 19 text

class OnboardingLayer : CALayer { let pictureFrame: CAShapeLayer let chair: CAShapeLayer let body: CAShapeLayer let leftArm: CAShapeLayer let rightArm: CAShapeLayer let table: CAShapeLayer let lamp: CAShapeLayer }

Slide 20

Slide 20 text

BOUNDS, ANCHORS, AND POSITIONS > Use paintcode to give bounds for a group > Use anchorPoint and bounds to calculate position

Slide 21

Slide 21 text

BOUNDS, ANCHORS, AND POSITIONS > Default AnchorPoint is (0.5, 0.5) > Use transform offset in paintcode to determine anchorpoint > Update position if you update anchorpoint!

Slide 22

Slide 22 text

NOW BREAK UP YOUR GROUPS

Slide 23

Slide 23 text

TAKE PAINTCODE OUTPUT AND... > Create a CALayer for each shape > Set the path of the CALayer to the vector output > Replace fill with fillColor > Add the CALayer to the parent layer

Slide 24

Slide 24 text

override init() { pictureFrame = OnboardingLayer.createPictureFrame() pictureFrame.anchorPoint = CGPointMake(0.5, 0); pictureFrame.bounds = CGRectMake(0, 0, 127, 98); pictureFrame.position = CGPointMake(64, 0); ... super.init() bounds = CGRectMake(0, 0, 296, 241); addSublayer(pictureFrame) addSublayer(chair) addSublayer(table) addSublayer(lamp) ... }

Slide 25

Slide 25 text

What does createPictureFrameLayer look like?

Slide 26

Slide 26 text

static func createPictureFrame() -> CAShapeLayer { let pictureFrameLayer = CAShapeLayer() // Color Declarations taken from paintcode let pictureBackgroundBlue = UIColor( red: 0.066, green: 0.485, blue: 0.695, alpha: 1.000) ... // pictureBackground Layer Shape let pictureBackgroundLayer = CAShapeLayer() pictureBackgroundLayer.path = UIBezierPath(rect: CGRect(x: 3.27, y: 1.48, width: 122.75, height: 94.65)).CGPath pictureBackgroundLayer.fillColor = pictureBackgroundBlue.CGColor pictureFrameLayer.addSublayer(pictureBackgroundLayer) ... return pictureFrameLayer }

Slide 27

Slide 27 text

Where Did That Come From?

Slide 28

Slide 28 text

If you don't break up your groups... //// rightArmGroup CGContextSaveGState(context) CGContextTranslateCTM(context, 186.17, 82.7) CGContextRotateCTM(context, -60.12 * CGFloat(M_PI) / 180) //// rightarm Drawing let rightarmPath = UIBezierPath() rightarmPath.moveToPoint(CGPoint(x: 44.94, y: -59.59))

Slide 29

Slide 29 text

RINSE AND REPEAT

Slide 30

Slide 30 text

WHAT WE HAVE SO FAR

Slide 31

Slide 31 text

RETROSPECTIVE > SpriteKit > QuartzCode (WYSIWYG For Core Animation + CALayers) > Squall (After Effects to iOS Code)

Slide 32

Slide 32 text

NOW FOR THE FUN PART!

Slide 33

Slide 33 text

CATRANSACTIONS

Slide 34

Slide 34 text

class IntroView : UIView { static let onboardingIntroArmAngle = CGFloat(0.610865) let illustrationLayer: OnboardingLayer = OnboardingLayer() var introAnimationOffset = CGFloat(0) func configureForIntroAnimation() { introAnimationOffset = (bounds.size.width / 2.0) + (illustrationLayer.bounds.size.width / 2) illustrationLayer.chair.updatePosition( offset: CGPointMake(-introAnimationOffset, 0)) illustrationLayer.pictureFrame.updatePosition( offset: CGPointMake(-introAnimationOffset, 0)) // ...

Slide 35

Slide 35 text

illustrationLayer.pictureFrame.transform = CATransform3DMakeRotation(CGFloat(20).degreesToRadians, 0, 0, 1); illustrationLayer.leftArm.transform = CATransform3DMakeRotation(-IntroView.onboardingIntroArmAngle, 0, 0, 1); illustrationLayer.rightArm.transform = CATransform3DMakeRotation(IntroView.onboardingIntroArmAngle, 0, 0, 1); }

Slide 36

Slide 36 text

func startIntroAnimation() { CATransaction.begin() CATransaction.setAnimationDuration(0.5) CATransaction.setAnimationTimingFunction( CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)) illustrationLayer.chair.updatePosition( offset: CGPointMake(introAnimationOffset, 0)) illustrationLayer.pictureFrame.updatePosition( offset: CGPointMake(introAnimationOffset, 0)) ... CATransaction.commit() }

Slide 37

Slide 37 text

CABASICANIMATIONS

Slide 38

Slide 38 text

Picture frame angle tranform animation with wobble lazy var pictureFrameAnimation: CAAnimation = { let pictureAnimation = CAKeyframeAnimation() pictureAnimation.beginTime = CACurrentMediaTime() + 0.25 pictureAnimation.keyPath = "transform.rotation" pictureAnimation.values = [0, -30.degreesToRadians, -10.degreesToRadians, -20.degreesToRadians] pictureAnimation.duration = 0.75 pictureAnimation.keyTimes = [0, 0.33, 0.66, 1] pictureAnimation.timingFunctions = [ CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut), CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut), CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn), ] return pictureAnimation }()

Slide 39

Slide 39 text

No content

Slide 40

Slide 40 text

Add the animations illustrationLayer.leftArm.addAnimation( leftArmAnimation, forKey: "leftArmIntroAnimation") illustrationLayer.rightArm.addAnimation( rightArmAnimation, forKey: "rightArmIntroAnimation") illustrationLayer.pictureFrame.addAnimation( pictureFrameAnimation, forKey: "pictureFrameIntroAnimation")

Slide 41

Slide 41 text

Keyframe animation static func armAnimation() -> CAKeyframeAnimation { let armAnimation = CAKeyframeAnimation() armAnimation.beginTime = CACurrentMediaTime() + 0.25 armAnimation.keyPath = "transform.rotation" // Note we animate rotation armAnimation.duration = 0.4 armAnimation.timingFunctions = [ CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut), CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn), ] return armAnimation }

Slide 42

Slide 42 text

Use the base animation and modify it per arm static func armAnimation() -> CAKeyframeAnimation { ... } lazy var leftArmAnimation: CAAnimation = { let leftAnimation = IntroView.armAnimation() leftAnimation.values = [0, 47.degreesToRadians, IntroView.onboardingIntroArmAngle] leftAnimation.keyTimes = [0, 0.5, 1] return leftAnimation }()

Slide 43

Slide 43 text

No content

Slide 44

Slide 44 text

ANIMATION GROUPS

Slide 45

Slide 45 text

static func identityTransformAnimation() -> CAAnimation { ... } static func fadeAnimation() -> CAAnimation { ... } static func largeFireworkAnimationGroup() -> CAAnimationGroup { let animationGroup = CAAnimationGroup() animationGroup.repeatCount = HUGE animationGroup.animations = [identityTransformAnimation(), fadeAnimation()] return animationGroup } func configureFireworkAnimation() { let fireworkAnimationGroup = IntroView.largeFireworkAnimationGroup() fireworkAnimationGroup.duration = 3.5 illustrationLayer.topLeftFireworkMaskLayer.addAnimation( fireworkAnimationGroup, forKey: "fireworkGroupAnimation") }

Slide 46

Slide 46 text

No content

Slide 47

Slide 47 text

CATRANSACTIONS PT2

Slide 48

Slide 48 text

CATransation Completion Blocks pt 1 Start your first transaction CATransaction.begin() CATransaction.setAnimationDuration(0.45) CATransaction.setAnimationTimingFunction( CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)) ...

Slide 49

Slide 49 text

CATransation Completion Blocks pt 2 Setup your completion block // After moving the main items into the center, // animate the arms and legs back to their identity state CATransaction.setCompletionBlock { [unowned self] in CATransaction.begin() CATransaction.setAnimationDuration(0.2) CATransaction.setAnimationTimingFunction (CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)) self.illustrationLayer.leftArm.transform = CATransform3DIdentity self.illustrationLayer.rightArm.transform = CATransform3DIdentity self.illustrationLayer.leftLeg.transform = CATransform3DIdentity CATransaction.commit() }

Slide 50

Slide 50 text

... // Move the character to center illustrationLayer.femaleBody.updatePosition( CGPointMake(introAnimationOffset, 0)) illustrationLayer.femaleHairBackground.updatePosition( CGPointMake(introAnimationOffset, 0)) // Scale trees back up illustrationLayer.trees.transform = CATransform3DIdentity CATransaction.commit()

Slide 51

Slide 51 text

No content

Slide 52

Slide 52 text

BONUS CATRANSITIONS!

Slide 53

Slide 53 text

extension CATransition { static func simpleFade() -> CATransition { let fadeTransition = CATransition() fadeTransition.duration = 0.125 fadeTransition.type = kCATransitionFade fadeTransition.timingFunction = CAMediaTimingFunction( name: kCAMediaTimingFunctionEaseInEaseOut) return fadeTransition } }

Slide 54

Slide 54 text

FADE TRANSITION IN USE let simpleFadeTransition = CATransition.simpleFade() titleLabel.layer.addAnimation(simpleFadeTransition, forKey: "changeTextTransition")

Slide 55

Slide 55 text

No content

Slide 56

Slide 56 text

WRAPPING UP > Use an asset pipeline that works for what you're building > Combine CAAnimations using CAAnimationGroups and by chaining CATransactions > CATransitions are simple and effective

Slide 57

Slide 57 text

QUESTIONS?