Homework is due tonight. No more office hours. Good luck fuckers.

Sally is gonna go around and make sure you guys have valid project ideas. If you don’t, she’s going to write it down and I’ll demand it sometime very soon.

Today, we have a much better presentation than the past few lecture. Today, we’re focusing just on animations. Animations are integral to many iOS apps.

So, everyone should download the skeleton app that’s online (YUP WE’RE NOT STARTING FROM SCRATCH WUUUUUUT)

Download the skeleton app, and look through it. We’ll talk about the structure. Blah blah blah. Basically, we’ll go through each of the animations, discuss what it should look like, and then demonstrate how to write it. Your final project will require some sort of animation, so pay attention.

Also, feel free to look at the other code that was written in this app. It’s a good example of what a decently written app looks like (because we haven’t really written one so far). You might need to google what “final internal func” means.

Basically, for each class, we have a view passed in, and we get to animate it somehow. We implement the function animate:.

The first one is a two color animation. We’re just gonna morph the color of the square.

There are two ways we can do this. One is through UIView, where it’s built-in to the class itself. The other is by making the animation from scratch. We’ll build the first and then we’ll show the second.

The first is simple:

UIView.animateWithDuration(1.0, animations: { () -> Void in
	view.backgroundColor = UIColor.purpleColor()
})

Talk about lambda function syntax, blah blah. One little buggy bug is that once we show this animation, the color of the thing will stay purple. We need to add a line to fix this problem. No problem.

The second way to accomplish this task is to build the animation from scratch. This uses CABasicAnimation, which accesses the layer of the view.

let animation: CABasicAnimation = CABasicAnimation(keyPath: "backgroundColor")
animation.duration = 1.0
animation.fromValue = UIColor.redColor().CGColor
animation.toValue = UIColor.purpleColor().CGColor
view.layer.addAnimation(animation, forKey: "backgroundColorFade")

Great—oh, uh-oh. The color didn’t stick. Yup. When you’re using CABasicAnimation, the animation resets. You need to stick it afterwards.

Cool, no problem! Great! You guys just wrote your first animation. Now, let’s move the square. I’m sure you guys can imagine how to do that—it’s very similar!

var initialPoint: CGPoint!
    
override func animate(view: UIView) {
    if (self.initialPoint == nil) {
        self.initialPoint = view.center
    } else {
        view.center = self.initialPoint
    }    
 
    let endingPoint: CGPoint = CGPoint(x: initialPoint.x + 50,
                                       y: initialPoint.y + 50)
    UIView.animateWithDuration(1.0, animations: { () -> Void in
        view.center = endingPoint
    })
}

Alternatively, with CABasicAnimation:

let useUIViewAnimations: Bool = false
var initialPoint: CGPoint!
    
override func animate(view: UIView) {
    if (self.initialPoint == nil) {
        self.initialPoint = view.center
    } else {
        view.center = self.initialPoint
    }
    
    let endingPoint: CGPoint = CGPoint(x: initialPoint.x + 50,
                                       y: initialPoint.y + 50)
    
    if (self.useUIViewAnimations) {
        UIView.animateWithDuration(1.0, animations: { () -> Void in
            view.center = endingPoint
        })
    } else {
        let animation: CABasicAnimation = CABasicAnimation(keyPath: "position")
        animation.fromValue = NSValue(CGPoint: self.initialPoint)
        animation.toValue = NSValue(CGPoint: endingPoint)
        animation.duration = 1.0
        animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionDefault)
        view.layer.addAnimation(animation, forKey: "MoveAnimation")
        view.center = endingPoint
    }
}

So, hopefully you see that CABasicAnimation affords you a lot more customizability. This is kinda related to how using CALayers affords you a lot of customizability with Graphics.

Because customizability is nice, we’re going to stick with using CABasicAnimations. It’s good to know that the other way exists, because it affords you a quick and easy animation.

Now, let’s talk about axes and rotations. The axes are what you expect. x-axis goes left-to-right. y-axis goes up-and-down. z-axis goes in-and-out. The iPhone kinda simulates 3D space, even though it’s displayed on a 2D screen. We’ll see an example of a 3D rotation in a little bit. For now, let’s stay simple. We want to rotate around the z-axis. This is a standard rotation that you expect when I say “rotation”. Think about it.

So, let’s do that. Really easy.

let animation = CABasicAnimation(keyPath: "transform.rotation.z")
animation.fromValue = 0
animation.toValue = 4 * M_PI
animation.duration = 2.0
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
view.layer.addAnimation(animation, forKey: "Z-Rotate")

Now, let’s do some more complication rotation. Some 3D looking rotation. To do this, we might wish to look into something called the CAAnimationGroup. It’s basically several animations mushed into one. Let’s do an animation that rotates around the z-axis AND the x-axis. Animation groups basically smush a bunch of animations into one singular animation.

So, the idea is you can set global properties about all the animations in the group. So, we could say

func rotationAnimation() -> CABasicAnimation {
    let animation = CABasicAnimation()
    animation.fromValue = 0
    animation.toValue = 4 * M_PI
    return animation
}

override func animate(view: UIView) {
    let zAnimation = self.rotationAnimation()
    zAnimation.keyPath = "transform.rotation.z"
        
    let xAnimation = self.rotationAnimation()
    xAnimation.keyPath = "transform.rotation.x"
        
    let group = CAAnimationGroup()
    group.duration = 2.0
    group.repeatCount = Float.infinity
    group.animations = [zAnimation, xAnimation]
    group.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
    view.layer.addAnimation(group, forKey: "3D-rotation")
}

So now, you guys know how to use CABasicAnimation to change color, move stuff, and rotate stuff. Basically, anything you can do with CALayers can probably be animated. This involves changing size, adding shadow, rounding the edges, opacity, border width,etc.

Now of course, you may wish to do more complicated animations. Maybe instead of changing from red to purple you might do red to purple to green to blue to pink to white. There’s a way to do it using UIView which is kinda wonky, and then there’s a right way to do it. Let’s explore the first way.

You can chain animations. You use multiple lambda completing functions. So, if you want to cycle through the rainbow (ROYGBP), it looks like this:

<< REDACTED >>

Incidentally, NOT GREAT. But, using the completing block, you can chain tons of animations around. Now, there’s something a bit more simpler we can use called a Keyframe Animation. These kinds of animations are like CABasicAnimations. However, in a CABasicAnimation, you only give a start and an end point, the your iPhone tries to interpolate everything in between. 

With a CAKeyframeAnimation, you can specify some of the interpolation points. Basically, you add a few more points into the mix, and your iPhone will interpolate to all of them. So, with the color example, you could just say start and end is just red to purple, but the iPhone interpolation will skip all of the colors in between. So, if we add in all the other colors of the rainbow, the iPhone will interpolate to them.

So, basically, with a CAKeyframeAnimation, you give it an Array of values to hit, and it will hit all of those values, just as if it were a chained animation. The code looks like this:

let animation = CAKeyframeAnimation(keyPath: "backgroundColor")
let arrayColors: [AnyObject] = [UIColor.orangeColor().CGColor,
                                UIColor.yellowColor().CGColor,
                                UIColor.greenColor().CGColor,
                                UIColor.blueColor().CGColor,
                                UIColor.purpleColor().CGColor,
                                UIColor.redColor().CGColor]
animation.values = arrayColors
animation.duration = 0.5
animation.repeatCount = Float.infinity
view.layer.addAnimation(animation, forKey: "MulticolorAnimation")

Notice that because of a bug, [CGColor] isn’t really a thing. It has to be manually cast to an [AnyObject] and then the animation takes care of it. Side note: anyone that can recreate nyan cat instantly passes the classes.

The same idea applies to movement:

let initialPoint = view.center
let secondPoint = CGPoint(x: initialPoint.x + 50, y: initialPoint.y)
let thirdPoint = CGPoint(x: secondPoint.x, y: secondPoint.y + 50)
let fourthPoint = CGPoint(x: thirdPoint.x - 50, y: thirdPoint.y)
let points = [NSValue(CGPoint: initialPoint),
              NSValue(CGPoint: secondPoint),
              NSValue(CGPoint: thirdPoint),
              NSValue(CGPoint: fourthPoint),
              NSValue(CGPoint: initialPoint)]
let animation = CAKeyframeAnimation(keyPath: "position")
animation.values = points
animation.duration = 0.5
animation.repeatCount = Float.infinity
view.layer.addAnimation(animation, forKey: "Multipoint")

The same idea applies to size. Except, the CALayer is NOT in charge of size. The UIView is. So, if we want to screw with size, we can say:

let initialFrame = view.frame
let secondFrame = CGRectInset(initialFrame, -30, -50)
let thirdFrame = CGRectInset(secondFrame, 10, 15)
let fourthFrame = CGRectInset(thirdFrame, -15, -20)
let animation = CAKeyframeAnimation(keyPath: "frame")
UIView.animateWithDuration(0.1, animations: { () -> Void in
    view.frame = secondFrame
}, completion: { (finished) -> Void in
    UIView.animateWithDuration(0.1, animations: { () -> Void in
        view.frame = thirdFrame
    }, completion: { (finished) -> Void in
        UIView.animateWithDuration(0.1, animations: { () -> Void in
            view.frame = fourthFrame
        }, completion: { (finished) -> Void in
            UIView.animateWithDuration(3.0, animations: { () -> Void in
                view.frame = initialFrame
            })
        })
    })
})

Which is a pretty cool effect, to say the least.

Now, make your own animation in the CustomAnimationViewController. Either make your own or make the one we show.

One important thing to cover is UIBezierCurves. How do you make something that’s curved? Well, the basic idea of a bezier curve is a line with control points that INFLUENCE the line. You don’t really need to know this, but it’s good to know if you want to do curved paths. We’ll just give you the coordinates here. To animate:

let path = UIBezierPath()
path.moveToPoint(view.center)
        
let initialPoint = view.center
var controlPoint1 = CGPoint(x: initialPoint.x,
                            y: initialPoint.y - 125.0)
var controlPoint2 = CGPoint(x: controlPoint1.x + 40.0,
                            y: controlPoint1.y - 125.0)
var destination = CGPoint(x: initialPoint.x + 80,
                          y: initialPoint.y)
        
path.addCurveToPoint(destination, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
        
controlPoint1.x += 80.0
controlPoint2.x += 80.0
destination.x += 80.0
       
path.addCurveToPoint(destination, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
        
let animation = CAKeyframeAnimation(keyPath: "position")
animation.path = path.CGPath
animation.duration = 1.5
view.layer.addAnimation(animation, forKey: "bezier")
        
view.center = destination

Watch that fucker bounce off the screen.