Final project meetings. Some people have already have them. Most of you will have them on Wednesday. Make sure you sign up! You should have your project idea ready, and maybe some wireframe mockups if you want to be really prepared.

Today, we’re making a really cool app called FaceTilt. This app REQUIRES that you have an iOS device connected. This is because the iOS simulator has no camera nor accelerometer/gyroscope. If you want to try this app out, check out the tidbit! It’s really quick and really easy. Do you guys want to take 15 minutes to set that all up now? Or fuck it?

Okay demo the app. Cool. We have two parts to this app—the camera controls, and the motion controls. These are two different APIs. We’ll start with the camera controls. Let’s look at the starter code on the website. When we run it, we see we have some basic controls that do nothing. The Start button takes us to the next screen. Tapping brings us back. Very straightforward.

If you don’t think you can recreate the handout from scratch, review it and email us asking for help!

PictureViewController is our main view controller. Let’s start by populating the main picture. If there’s no already existing image, let’s replace it with a placeholder image. Also, let’s round the image view out to be a circle.

    override func viewDidLoad() {
        super.viewDidLoad()
        if (self.pictureImageView.image == nil) {
            self.pictureImageView.image = UIImage(named: "no_image.jpg")
        }
        self.pictureImageView.layer.cornerRadius =
            self.pictureImageView.bounds.size.height / 2.0
        self.pictureImageView.layer.masksToBounds = true
    }

Great. So, now, let’s add the functionality to change the image inside the circle view. For this, we will use a UIImagePicker. What’s going to happen is, when we hit the button, we’re going to bring up a UIActionSheet. Who knows what this is?

It’s a little popup of options on the bottom of your screen. You’ll recognize it when you see it. First, we must subscribe to UIActionSheetDelegate.

    @IBAction func selectImageButton(sender: AnyObject) {
        var actionSheet: UIActionSheet = UIActionSheet(
            title: "Photo Picker", delegate: self,
            cancelButtonTitle: "Cancel",
            destructiveButtonTitle: nil,
            otherButtonTitles: "Photo Library", "Take Photo")
        actionSheet.showInView(self.view)
    }

Now, since we’ve subscribed to UIActionSheetDelegate, you can expect that we’re going to write a function to handle the input from the UIActionSheet. Indeed, we have this stub, and it’s actionSheet(: clickedButtonAtIndex).

Basically, when a button is clicked, it spits out an index. That index represents the number of the button as specified. We can use actionSheet.cancelButtonIndex, .destructiveButtonIndex, .firstOtherButtonIndex to grab indices we need (note that the latter doesn’t work because deprecated in iOS8).

So, if the user selects photo library, we want to bring that up. If the user selects camera, we want to bring that up ONLY IF there is a camera—otherwise, we default to the photo library. Note that we must subscribe to UIImagePickerControllerDelegate, and weirdy enough, UINavigationControllerDelegate.

    func actionSheet(actionSheet: UIActionSheet, clickedButtonAtIndex buttonIndex: Int) {
        if (buttonIndex == actionSheet.cancelButtonIndex) {
            return
        }
        
        let imagePicker: UIImagePickerController = UIImagePickerController()
        imagePicker.allowsEditing = true
        imagePicker.delegate = self
        imagePicker.sourceType = .PhotoLibrary
        if (buttonIndex == 2 && UIImagePickerController.isSourceTypeAvailable(.Camera)) {
            imagePicker.sourceType = .Camera
        }
        self.presentViewController(imagePicker, animated: true, completion: nil)
    }

Now, everything works! The only issue is how do we get the image back from the UIImagePicker? You guessed it: delegation.

    func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
        var image: UIImage = info[UIImagePickerControllerEditedImage] as UIImage
        dismissViewControllerAnimated(true, completion: nil)
        self.pictureImageView.image = image
    } 

And that’s it! Easy enough! You’ve done the Face part of FaceTilt. Bonus question: does anyone know how Snapchat gets its custom camera view?

Set imagePicker.cameraControls to False and use an overlay view.

Now the Tilt part. First, let’s try to pass the picture over to the tilt view. Add imageView to the Tilt Controller.

    var imageView: UIImageView = UIImageView()

Then: 

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
        if var secondViewController = segue.destinationViewController as? TiltViewController {
            secondViewController.imageView.image = self.pictureImageView.image!
        }

    }

CoreMotion is our API of choice here. Before iOS 8, there were only three main parts to CoreMotion: the Accelerometer, the Gyroscope, and the Magnetometer. With iOS 8, CoreMotion also has barometry—keeps track of altitude with CMAltimeter—and pedometrics with CMPedometer. People with iPhone 6’s can actually use the new features, but today we’ll just focus on the first two: accelerometer and gyroscope.

The accelerometer is concerned with determining proper acceleration—basically, the g-forces acting on the device. So, let’s draw pictures with axes.

When the phone is face-up, the g-forces it is experience is in the negative z-direction. Face-down, position z-direction. Pointed upwards? Negative y-direction. It basically always goes the same direction as gravity. This data is also affected by any acceleration applied to your iPhone, but we won’t delve into that this lecture. For the record, do not ever apply acceleration to your iPhone—this is generally considered a bad idea.

The gyroscope is concered with rotation. Similarly, it has three components, x, y, and z, to tell you how much the object has rotated around each axis. 

Okay, let’s see how we’re going to manage all of this. We’ve pre-written a Marble class to handle all of the physics! We simply give it an x acceleration, a y acceleration, and a z rotation and it figures it all out for us. Isn’t that nice?

Basically, the Marble is going to server as our Model while we update a UIImageView according to the Marble.  So, let’s first start by getting together all the things we need.

class TiltViewController: UIViewController {
    let kTiltViewControllerMarbleSize: CGFloat = 150.0
    var imageView: UIImageView = UIImageView()
    var motionManager: CMMotionManager = CMMotionManager()
    var marble: Marble!

The CMMotionManager is kinda a catch-all data class. It produces all the data we’ll need from the accelerometer and gyroscope (and magnetometer).

    override func viewDidLoad() {
        super.viewDidLoad()
        // initialize values for marble to start out at (0,0) which is in upper left corner for UIViews
        var marbleSize: CGSize = CGSizeMake(kTiltViewControllerMarbleSize, kTiltViewControllerMarbleSize)
        self.imageView.frame = CGRect(origin: CGPointZero, size: marbleSize)
        // make the image appear circular
        self.imageView.layer.cornerRadius = kTiltViewControllerMarbleSize / 2.0
        self.imageView.clipsToBounds = true
        // create marble
        self.marble = Marble(size: marbleSize)
        
        // tell CMMotionManager to start collecting data from accelerometer and gyroscope
        self.motionManager.startAccelerometerUpdates()
        self.motionManager.startGyroUpdates()
        
        // make sure to add the "marble image" to the view
        self.view.addSubview(self.imageView)
        
        // set up a tap gesture recognizer so that you can tap to go back
        // to the PictureViewController
        self.tap.addTarget(self, action: "tappedView")
        self.view.addGestureRecognizer(tap)
        
        // create a timer to get updates for accelerometer/gyro constantly so that marble moves around
        // according to accelerometer and gyroscope readings
        NSTimer.scheduledTimerWithTimeInterval(0.0, target: self, selector:"updateMarble:", userInfo: nil, repeats: true)
        
    }

Okay, so there are really two ways to get information from the CMMotionManager. I refer to them as pushing and pulling. Pushing data means the MotionManager has a function that it calls every so often, to which it passes data. Pulling data, internally speaking, has a bit less overhead, and we basically end up polling the CMMotionManager at a specified interval. We’re doing the latter. But now we have to handle this method, updateMarble:

    func updateMarble(timer: NSTimer) -> Void {
        // handle translations
        // make sure that accelerometer has collected data and then get the data
        // in order to update the marble and make the image move around
        if(self.motionManager.accelerometerData != nil) {
            var acceleration = MarbleAcceleration(xAccel: self.motionManager.accelerometerData.acceleration.x, yAccel: self.motionManager.accelerometerData.acceleration.y)
        
            self.marble.updateMarbleAcceleration(acceleration)
            
            self.imageView.center = self.marble.center()
        }
        
        // handle rotations about z axis
        // make sure that the gyroscope has collected data and then get the data
        // in order to make the marble rotate around
        if(self.motionManager.gyroData != nil) {
            self.marble.updateMarbleRotation(self.motionManager.gyroData.rotationRate.z)
            self.imageView.transform = self.marble.rotation()
        }

Through the power of print statements, we can see exactly what the data looks like.

In Marble, we can modify the constants