Advanced iOS Instruction: Clone WhatsApp with Bitfountain

280 video lectures that take you step by step through the process of creating a complete WhatsApp clone with Firebase
4.5 (70 ratings) Instead of using a simple lifetime average, Udemy calculates a
course's star rating by considering a number of different factors
such as the number of ratings, the age of ratings, and the
likelihood of fraudulent ratings.
545 students enrolled
$19
$50
62% off
Take This Course
  • Lectures 289
  • Length 13 hours
  • Skill Level Expert Level
  • Languages English
  • Includes Lifetime access
    30 day money back guarantee!
    Available on iOS and Android
    Certificate of Completion
Wishlisted Wishlist

How taking a course works

Discover

Find online courses made by experts from around the world.

Learn

Take your courses with you and learn anywhere, anytime.

Master

Learn and practice real-world skills and achieve your goals.

About This Course

Published 4/2016 English

Course Description

Course Description

Our WhaleTalk course teaches you how to build a complete WhatsApp clone in Swift 2.0 and iOS 9. This is not a toy app. You will be building a chat view controller that is fully responsive with auto layout - all from scratch. The chat functionality goes beyond person to person. Just like in WhatsApp, you will have the ability to start group chats and import contacts with the Contacts Framework. Data will be persisted to Core Data, and it will all be synced to the cloud with Firebase.

We believe students learn by building. There's no better way to become an iOS developer than building a complex app from scratch. 



Student Reviews:

"I must say that so far, this course is awesome. Having the challenging assignments, daily discussions and feedback from the instructors, has been the missing piece that I have been looking for. I have read a handful of books, watched hours of video & typed in a bunch of tutorials, and finally, having to work through tough assignments and applying what I have been learning, it is all starting to click - Finally!" - Mark S.

"This course is by far the most elaborate and best taught iOS course I have seen online yet. It's good structured and covers a lot of topics in-depth." - Christoph Zamaitat

"Bitfountain's discussion board is one of the best resources for a beginning iOS developer. So much help being offered" - Omar S.

"Great course for total beginners, but also a lot of tricks and tips for those with experience. Also good seeing how others code and tackle problems. A great learning tool what ever your skill level." - Mazza1111

"I've just completed the iOS course, which I thought was a great intro to the XCode environment... I feel it's been well worth the investment. " - Herdy H.

"I am about a quarter of the way through this course and have no previous programming experience. I have found this course to be well presented and structured with everything explained clearly. This is a difficult topic and you have to work hard understanding the concepts if you are new to it, but it is easy to go back over an area to pick up anything you might have missed first time round. The guys are constantly improving it and adding to it and seem committed to getting it 100% right. Recommend it....but be prepared to work hard!!" - Tony McDonald

"Can't reiterate it enough how this course is helping me with my iOS dev skills. I think using protocols and delegation is finally becoming second nature. Fingers crossed :-)" -Alex P.

"I am really loving the class. I have taken classes at Code School & Treehouse and both were missing a key element. The ability to ask questions as you complete a section and get an answer. " -Lisa A.

"Your training is the best out there so far. I wish I had the time away from regular job to follow along." -Christian S.

"Im loving this.. I have been through at least 5 books, and many online deals. Yours is super so far. Finally, i can get from start to finish on a lesson without wondering why on "Everything" thank you. Cant wait to do more.. " -Kevin R.


What are the requirements?

  • Knowledge of Swift and iOS Development similar to what is covered in our iOS Development Course.
  • Ability to set up and use Core Data to a basic level. See our Core Data course.
  • Understand how to use Auto Layout to create responsive views. See our Auto layout course.

What am I going to get from this course?

  • How to architect an advanced application.
  • Creating complex views.
  • Modeling, updating, and reading dynamic data.
  • How to keep multiple devices synced through the cloud with Firebase.
  • Persisting data with Core Data.
  • Importing contacts with the Contacts Framework.

What is the target audience?

  • Anyone interested in learning how to build a complex app from beginning to end.

What you get with this course?

Not for you? No problem.
30 day money back guarantee.

Forever yours.
Lifetime access.

Learn on the go.
Desktop, iOS and Android.

Get rewarded.
Certificate of completion.

Curriculum

Section 1: Intro
01:27

Welcome to WhaleTalk our whatsapp clone. We will be integrating some cool frameworks and working on app architecture.

Section 2: Setup
02:29

In this lecture, we will start setting up our WhaleTalk project.

* Create a new Project in Xcode

* **New Project-> iOS Application-> Single View Application**

* Name it WhaleTalk

The first thing we'll do is rename the view controller from its default name of `ViewController` to `ChatViewController`.

* Change the view controller name in Xcode's **Project Navigator** by clicking once on the file's name and entering the new name

![change File name](notes/1-ChatViewController.png)

But we also need to change the Class name. This is actually much more important than changing the file's name.

* Change the view controller's class from `ViewController` to `ChatViewController`

![change class](notes/1-ChatViewControllerClass.png)'

We finally need to update the class in the storyboard which manages the ViewController on the screen. 

![identity inspector](notes/1-ChatViewControllerIdentityInspector.png)

Let's make sure everything still works and that we haven't broken the connection between the View Controller file and the storyboard.

* Run the app

We'll see an empty screen but we shouldn't get any errors.

04:30

In this lecture, we will add a TableView to our project.

We've added TableViews before but using the Storyboard. Now we will be creating the TableView completely in code.

We will start by creating a constant for our TableView, right below the class declaration.

**Code:**

    private let tableView = UITableView()

All we did was create a constant attribute name tableView and created the instance. The private keyword might be new to a few of you. Essentially adding this before any attribute will make the constant or variable private to the class which creates it. What does that mean? Other classes which may attempt to use the ViewController will not be able to access this attribute only the class which creates the private attribute may use it.

Next, inside of `viewDidLoad()`, we will set up our constraints. We'll start by turning the auto resize mask off and then adding the TableView as a subview of our main view.

If this doesn't make sense to you, make sure to watch the Auto Layout lectures.

**Code:**

    tableView.translatesAutoresizingMaskIntoConstraints = false

    view.addSubview(tableView)

Now we will setup our constraints into an array so we can activate them all at once.

**Code:**

    let tableViewConstraints: [NSLayoutConstraint] = [

        tableView.topAnchor.constraintEqualToAnchor(view.topAnchor),

        tableView.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor),

        tableView.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor),

        tableView.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor)

    ]

    NSLayoutConstraint.activateConstraints(tableViewConstraints)

So, what we've done here is created an array of type `NSLayoutConstraint` and added all the constraints for our TableView in the array.

Finally, we activate all the constraints with one line of code. Very convenient.

* Run the app

You'll see an empty table, but that means everything is working correctly.

![table](notes/2-emptyTable.png)

Section 3: Add Data Source
02:26

In this lecture, we will add a data source for our table.
The first thing we will do is create a Message class, which will be responsible for our messages.

  • File-> New File-> iOS Source-> Swift File
  • Name it Message
  • Add the following code to the new Message file

Code:

class Message {
    var text: String?
}

Right now, this class only contains 1 property, an optional string variable called text.
We need to go back to ChatViewController to keep working.
Now let's create some stored properties or instance variables, as they are called in other languages, to our class.
Code:

private var messages = [Message]()
private let cellIdentifier = "Cell"

messages is an array of type Message, this is the class we just created.
Next we create a cell identifier so we can reuse our cells. This is a simple String constant.
Remember that both these lines go right below the class declaration, where we had the tableView declaration.
In our next lecture we will create some fake data for our table.

02:44

In this lecture, we will create some fake data for our table.

Still in **ChatViewController**, inside `viewDidLoad()`.

**Code:**

    for i in 0...10 {

      let m = Message()

      m.text = String(i)

      messages.append(m)

    }

So, what's happening here?

1. We have a loop that iterates from `0` to `10`

2. then we create the constant `m` as an instance of the `Message` class

3. and we set the value of `i` as the text property of the `m` instance. The variable `i` will start at 0 on our first pass through the loop and keep increasing all the way to `10`

4. finally, we append the instance of `m` to the `messages` array that we created earlier

This loop will generate an array of Message instances which will contain the numbers 0 through 10 as the value of its `text` string property.

If you want to see the values, you can print them out with code like this (this would go right below the loop above, but outside that loop's brackets). Lets try this out.

    for eachMessage in messages {

        print(eachMessage, ":", eachMessage.text)

    }

Run the app and see the values it prints out.

![fake values](notes/2-fakeDataValues.png)

I'm just printing each message type here just so you can see it's an instance of `WhaleTalk.Message`.

This is just a shortcut so we don't have to create it manually, one by one, but we could also have done that of course.

Make sure to delete this for loop after you see the result in the console so we don't clutter up the console.

In the next lecture, we will add the UITableView datasource extension and register the cell so we can reuse it.

05:20

In this lecture, we will register the cell for reuse and add the UITableView datasource.

Remember we want to reuse our cells, we never want to create new ones to display data if it's not necessary (see the UITableView lectures for more on this). We have usually done this in the Storyboard by giving the cell an identifier name.

This time we're doing it in code. Previously, we created the `cellIdentifier` constant. Now we need to tell Xcode to use that identifier to reuse the cell. It's actually a lot easier than it sounds.

**Code:**

    // inside viewDidLoad

    tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: cellIdentifier)

That's it. One single line. Now Xcode knows to reuse that cell, with the `cellIdentifier` we created. Simple.

We also need to create and assign our UITableView Datasource. To do this we will first create the extension to our class so that it conforms to the UITableView Datasource protocol and then we will assign the datasource.

This extension goes outside of the `ChatViewController` class declaration.

**Code:**

    extension ChatViewController: UITableViewDataSource {

        func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

            return messages.count

        }

        func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

            let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath:indexPath)

            let message  = messages[indexPath.row]

            cell.textLabel?.text = message.text

            return cell

        }

    }

![extension](notes/3-extension.png)

If you've done any of the **UITableView** lectures with us, this should be familiar to you. If not, let me go through it briefly.

In order to conform to the `UITableViewDataSource` protocol, we need at least 2 methods: `numberOfRowsInSection` and `cellForRowAtIndexPath`.

The first method returns an `Integer`, as you can see in its declaration. That number is the number of rows we want to display. So we just set it to the number of items in the `messages` array. That way, whenever the array changes, we're covered.

The second method is used to display the cell. So we first create a cell constant for the row.

Then we create a `message` constant and assign the `Message` instance for that row to it. We set the current cell's `textLabel` to the text of the `Message` instance for that row.

And, finally, we return the cell.

This is a brief overview, I'm not going into detail here because I assume you've done this before. If you haven't, I really encourage you to watch the `UITableView` lectures.

Alright, we have our class conforming to the `UITableViewDataSource` protocol. The only thing missing is telling the class who the datasource is. Let's do that now.

Inside `viewDidLoad()`.

**Code:**

    tableView.dataSource = self

We simply tell the class that the `UITableViewDataSource` is itself, since we've just made it conform to this protocol.

Section 4: Add Cell Model & Bubble for Message
04:34

In this lecture, we will add a cell Model to our project.

Why do we need a cell model? Again, check our UITableViewCell lectures for more, but basically, we want to have a way to display custom data in our cells. If we just wanted to display a title and text, we could use one of Xcode's predefined cell models. But since we want more control over how our cell looks, we will create our own model.

This will be a subclass of `UITableViewCell`.

* Create a new class
* **File-> New File-> iOS Source-> Cocoa Touch Class**
* **Class:** ChatCell
* **Subclass of:** UITableViewCell
* **Language:** Swift

This will create a new file called `ChatCell`.

Delete all the boilerplate code that Xcode creates, we will be creating our own methods here.

![boilerplate](notes/1-boilerPlate.png)

Let's start by creating a message label and an image view in our cell.

**Code:**

    let messageLabel: UILabel = UILabel()

    private let bubbleImageView = UIImageView()

Next, we will override the initialization method for the UITableViewCell so we can customize it as we want.

**Code:**

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

    }

So, what are we doing here? When you instantiate a class or struct, an initialization method is required. A lot of times, this is called behind the scenes automatically by Xcode. And that's fine. But in this case, we want to customize our cell so we are overriding the `init` method and we will add some instructions on how to initialize this `UITableViewCell`.

Specifically, we are calling the `init` method with `style` and `reuseIdentifier` parameters.

`style` is just an instance of `UITableViewCellStyle` which is an enum.

`reuseIdentifier` is just a string with whatever identifier we've set up to reuse the cell. Remember we already set this up earlier.

The second line of code simply calls the init method of the super class.

See the `Classes` and `Structs` lectures for more on all this. This is not what we're really interested in here. The interesting stuff is what we're going to put inside this init method.

We will start configuring the cell in our next lecture. 

08:07

In this lecture, we will configure the cell model we added earlier.

Alright, time to tackle our cell model. We will be writing all this code inside the `init` method we created in the last lecture.

In the last lecture, we created a `UILabel` and a `UIImageView` as properties for our `ChatCell`. Now it's time to configure those properties. We will add Auto Layout constraints to them and get them ready for use.

We are going to use a speech bubble image and have the message text inside of it. The image looks like this:

![bubble](notes/2-MessageBubble.png)

We will add it to our project later on.

**Code:**

//In our initialization method add:

    messageLabel.translatesAutoresizingMaskIntoConstraints = false
    bubbleImageView.translatesAutoresizingMaskIntoConstraints = false
    contentView.addSubview(bubbleImageView)
    bubbleImageView.addSubview(messageLabel)

As usual with Auto Layout, we set the resizing mask to false and add our views as subviews.

We will now center the `UILabel`, smack in the middle of the `UIImageView` with 2 constraints.

**Code:**

    messageLabel.centerXAnchor.constraintEqualToAnchor(bubbleImageView.centerXAnchor).active = true
    messageLabel.centerYAnchor.constraintEqualToAnchor(bubbleImageView.centerYAnchor).active = true    

We want the speech bubble image to grow with the text so we'll do that with constraints.

**Code:**

    bubbleImageView.widthAnchor.constraintEqualToAnchor(messageLabel.widthAnchor, constant:50).active = true
    bubbleImageView.heightAnchor.constraintEqualToAnchor(messageLabel.heightAnchor).active = true

Note that we are adding `50` to the width, this is to account for the speech bubble's tail.

We now what to place the bubble at the top right of the cell.

**Code:**

    bubbleImageView.topAnchor.constraintEqualToAnchor(contentView.topAnchor).active = true
    bubbleImageView.trailingAnchor.constraintEqualToAnchor(contentView.trailingAnchor).active = true

Let's configure the `UILabel` next.

**Code:**

    messageLabel.textAlignment = .Center
    messageLabel.numberOfLines = 0

The last thing we'll do is get our `UIImageView` ready for use.

**Code:**

    let image = UIImage(named: "MessageBubble")?.imageWithRenderingMode(.AlwaysTemplate)
    bubbleImageView.tintColor = UIColor.blueColor()
    bubbleImageView.image = image    

Here we create a `UIImage` with an image named _MessageBubble_ (we will use that **same name** when we import the image), we give it a tint color and then we add it as an image to the `bubbleImageView` `UIImageView`.

What's interesting here, which you might not have seen before, is the `imageWithRenderingMode` method. This allows us to tint the image however we want.

The `.AlwaysTemplate` mode draws the image ignoring its color information. So it's up to us to specify what color we want it.

This is a great way to be able to change our image's color in code.

01:58

In this lecture, we will finish configuring the cell.

We need to add another initializer because we have created a custom initializer.

**Code:**

    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

We need to do this because in Swift, a convenience initializer must ultimately call a designated initializer. This means that we need to call some built-in initializer at some point. Since our initializer is a custom one, we also need to make sure that our class is properly initialized by calling a "regular" initializer.

If this doesn't mean anything to you, don't worry. It's enough for now to know that when we create a custom initializer, we should also call the `init(coder aDecoder: NSCoder)` initializer.

For more on class initialization, see our **Classes and Structs** lectures.

02:32

In this lecture, we will add the speech bubble image asset and configure it so we can use a single size for all the iOS devices.

* Download the `MessageBubble.pdf` asset from this lecture
* Drag it to **Assets.xcassets** in Xcode

![xcassets](notes/4-xcassets.png)

* With the `MessageBubble` selected, change **Scale Factors** in the **Attributes Inspector** to **Single Vector**

![scale factors](notes/4-scaleFactors.png)

* Drag the `MessageBubble` from where it is now in **Unassigned** to **All Universal** above it

![universal](notes/4-drag.png)

Our image is ready to be used for different sizes.

![ready](notes/4-ready.png)

Let me explain what we've done.

These days there are a number of iOS devices, with different screen sizes and resolution. We have retina and non-retina displays as well. So it breaks down like this:

- Any non-retina device will use image sizes at **1x** (iPhone 3 and below)
- Retina devices (iPhone 4 and above) will use images at **2x** size
- iPhone 6 Plus will use images at **3x** size

**2x** means twice as big and **3x** means 3 times as big.

This means that whenever we create an image to use in our apps, we need to create all 3 sizes for every single image. There are ways to automate this but it can get tiresome.

So there is a trick to be able to use a single image and tell Xcode to resize it. And that is just what we've done above.

We told Xcode to use a single image as Universal for all sizes.

Before you pop the champagne though, there are limitations to this trick. This will only work with **resizable vector** images that have been saved as PDF or EPS.

For more on vector and bitmap images, check out the **Single Image Asset** lecture where we explain all this in depth.

02:26

In this lecture, we will edit the `ChatViewController` to use the new cell model we just created as well as the new image we added.

Earlier, we had registered the cell for reuse. We're going to do the same thing, but in this case, we will use the new cell model we created so we'll edit the `tableView.registerClass` line of code inside `viewDidLoad()`.

**Code:**

    tableView.registerClass(ChatCell.self, forCellReuseIdentifier: cellIdentifier)

So here, instead of using an instance of `UITableViewCell`, we use an instance of our `ChatCell`.

This means we'll also need to change the instance of cell inside the `cellForRowAtIndexPath` method so that it uses the instance of our new cell model as well.

**Code:**

    let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath:indexPath) as! ChatCell

Notice that we have to cast it as `ChatCell` to help out the compiler.

We also added a `messageLabel` property to our cell so we need to remove the line that previously set the cell's `textLabel` and add code for our `messageLabel`.

So delete this line: `cell.textLabel?.text = message.text`. And add the one below.

**Code:**

    cell.messageLabel.text = message.text

Ok, we've made a number of changes to our structure. Let's run the app and make sure everything still works.

* Run the app

You should see the speech bubbles in blue and the numbers inside it.

![bubbles](notes/5-bubbles.png)

The numbers are hard to see (especially in the screenshot) because the text is black, but they're there.

Section 5: Incoming Messages
02:36

In this lecture, we will setup our app to deal with incoming messages.

We need to make a distinction for messages that are incoming. In order to do that, the first thing we'll do is add a new property to our **Message** class.

Open the `Message.swift` file and add this property.

**Code:**

    var incoming = false

We add the `incoming` boolean variable and set it to `false` by default.

With that taken care of, let's edit the `ChatViewController` to make our fake data have some incoming messages so we can test this new feature.

We'll create a new variable for the `incoming` state and then apply that to all the entries in the array.

Inside `viewDidLoad()`.

**Code:**

    // add this variable above the loop
    var localIncoming = true

    // inside the loop we will add these 2 new lines right above `messages.append(m)`
    m.incoming = localIncoming
    localIncoming = !localIncoming

So now our `viewDidLoad()` method looks like this for these changes:

![new code](notes/1-new-code.png)

Notice that we have a line that toggles `localIncoming` from `true` to `false` and vice-versa. This line:

    localIncoming = !localIncoming

Means: "take whatever value localIncoming is right now and set it to its opposite".

Since `localIncoming` is a boolean, it will be set to `false` if `true`, or to `true` if `false`.

Here's what's happening here:

1. the first item in the loop will have its `incoming` property set to `true` because the `localIncoming` variable we created is `true`
2. then the line that toggles the local `localIncoming` variable gets run and sets it to the opposite, in this case `false`
2. the second item in the loop, will have its `incoming` property set to `false`
3. then the `localIncoming` toggle is run again, setting it to `true`
4. the third item (third time the loop runs) will have its `incoming` property set to `true`
5. and so on...

So we'll end up with half the messages with `incoming` as `true` and half with it as `false`.

Since we're here, we will also add a call to the `incoming` function. This function doesn't exist yet and we will create it in a little while. It will take care of applying the right constraints to our bubble image.

We will do this in `cellForRowAtIndexPath`.

**Code:**

    cell.incoming(message.incoming)

![cellForRowAtIndexPath](notes/1-cellForRow.png)     

We're not ready to run the app just yet. We need to add this new feature to `ChatCell`. We'll do that in the next lecture.

01:49

In this lecture, we will edit `ChatCell` to add support for the `incoming` property.

We'll begin by adding 2 properties for Auto Layout constraints.

**Code:**

    private var outgoingConstraint: NSLayoutConstraint!
    private var incomingConstraint: NSLayoutConstraint!

![constraintsProperties](notes/2-constraintsProperties.png)

We'll have to edit the trailing anchor we set up earlier for the bubble image to use one of these new constraint properties.

Inside the `init` method.

Delete this line: `bubbleImageView.trailingAnchor.constraintEqualToAnchor(contentView.trailingAnchor).active = true`. We'll add the constraint again as the `outgoingConstraint`.

**Code:**

    outgoingConstraint = bubbleImageView.trailingAnchor.constraintEqualToAnchor(contentView.trailingAnchor)
    incomingConstraint = bubbleImageView.leadingAnchor.constraintEqualToAnchor(contentView.leadingAnchor)

The new code in context:

![new constraints](notes/2-newConstraints.png)

You'll notice that we didn't activate these new constraints. This is because we need to do some logic depending on whether the `incoming` property is set to true or false. We'll add a function for that. But we'll do it in the next lecture.

02:12

In this lecture, we will add a new function to handle constraints activation based on the `incoming` property.

Navigate to the ChatCell class and we'll add the necessary function to remove our error.

**Code:**

    func incoming(incoming: Bool) {
        if incoming {
            incomingConstraint.active = true
            outgoingConstraint.active = false
        } else {
            incomingConstraint.active = false
            outgoingConstraint.active = true
        }        
    }

This function is pretty simple. If `incoming` is `true`, then `incomingConstraint` is active and `outgoingConstraint` is inactive. If `incoming` is false, `incomingConstraint` is inactive and `outgoingConstraint` is active.

What do we gain with this? This is an easy way to set our bubble image either flush left or right, depending on the `incoming` value.

* Run the app

You should see the bubbles staggered. The ones that have `incoming` set to true to the left, the other ones to the right.

![staggeredBubbles](notes/3-staggeredBubbles.png)

Great, that means our new code is working perfectly. 

Section 6: Flip Bubble & Change Color
01:49

In this lecture, we will start coding to change the color and orientation of the incoming speech bubble.

So now we have our incoming images on the left. But the speech bubble looks wrong, it's oriented towards the right. It would look a lot nicer if it were oriented towards the left, like it's coming out of the margin.

Also, wouldn't it be great if we could have it a different color than the one on the right? Sure it would. Let's get things started.

We will do this in `ChatCell.swift`.

Earlier, we had this bit of code inside the initializer which would create an image instance, change its color and assign it to the `bubbleImageView`:

    let image = UIImage(named: "MessageBubble")?.imageWithRenderingMode(.AlwaysTemplate)
    bubbleImageView.tintColor = UIColor.blueColor()
    bubbleImageView.image = image

Now, we will let the `incoming` function handle this. So we'll need to delete the 3 lines of code above from the `init` method.

Inside the `incoming` function, let's add the code to handle this. We will add 2 lines of code. The first one inside the `if incoming`, at the end of everything. The second one inside the `else`, also at the end of everything.

**Code:**

    bubbleImageView.image = bubble.incoming

    bubbleImageView.image = bubble.outgoing

So the whole function looks like this:

![incoming](notes/1-incomingFunction.png)

Now, we'll get errors for these lines because we are calling the `bubble` object, which doesn't exist yet. We will fix that in the next lesson.

05:06

In this lecture, we will create the missing function to flip the speech bubble image.

At the end of the `ChatCell` class in this file, at the very end of the file, right after the closing curly brace, let's add the bubble object.

**Code:**

    let bubble = makeBubble()

This object will get assigned the return value from the `makeBubble()` function. Now, we haven't written this function. We will do so next.

The `let bubble` declaration in context:

![bubble](notes/2-letBubble.png)

The `makeBubble()` function will call another function which will take care of assigning the right color to the bubble. We will write that function in the next lecture. That means we'll get some errors in the `makeBubble()` function but those should only be for this missing function, we shouldn't be seeing any other errors.

Let's get to it. Right below the `bubble` declaration.

**Code:**

    func makeBubble() -> (incoming: UIImage, outgoing: UIImage) {
        let image = UIImage(named: "MessageBubble")!

        // rendering mode .AlwaysTemplate doesn't work when changing the orientation
        let outgoing = coloredImage(image, red: 0/255, green: 122/255, blue: 255/255, alpha: 1)

        let flippedImage = UIImage(CGImage: image.CGImage!, scale: image.scale, orientation: UIImageOrientation.UpMirrored)

        let incoming = coloredImage(flippedImage, red: 229/255, green: 229/255, blue: 229/255, alpha: 1)

        return (incoming, outgoing)
    }

Let's go over this function.

1. it takes no parameters
2. it returns a Tuple with 2 variables, one called `incoming`, of type `UIImage`, the other `outgoing`, also of type `UIImage`
3. we assign the **MessageBubble** image to the `image` constant
4. we create the `outgoing` constant and give it a color
5. we create the `flippedImage` constant, which is just the same MessageBubble image but flipped using a different initializer for `UIImage`
6. we create the `incoming` image, which takes the `flippedImage` we just created
7. lastly, we return the tuple with both variables

One thing you might not have seen before is the different initializer for `UIImage`. We usually use `UIImage(named: String)`. We've actually used it at the beginning of the `makeBubble` function.

But `UIImage` has other initializers as well. For `flippedImage`, we are using one that allows us to manipulate its orientation to easily flip it. This is great, because it means we can use a single image asset and not ask our designer for another one just because it's flipped. 

Notice that we couldn't use the `.AlwaysTemplate` rendering mode because it doesn't work when we change the image's orientation. So we have to take care of coloring the image ourselves.

We will do that in the next lecture by creating the `coloredImage` function.  

06:26

In this lecture, we will create the missing `coloredImage` function.

Right below the `makeBubble()` function.

**Code:**

    func coloredImage(image: UIImage, red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) -> UIImage! {
        let rect = CGRect(origin: CGPointZero, size: image.size)
        UIGraphicsBeginImageContextWithOptions(image.size, false, image.scale)
        let context = UIGraphicsGetCurrentContext()
        image.drawInRect(rect)

        CGContextSetRGBFillColor(context, red, green, blue, alpha)
        CGContextSetBlendMode(context, CGBlendMode.SourceAtop)
        CGContextFillRect(context, rect)
        let result = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return result
    }

OK, let's see what this function does:

1. it takes 5 parameters, the first one a `UIImage` and the rest just `CGFloat` numbers
2. it returns a `UIImage`
3. we create a rectangle with `CGRect` with the image's size
4. we then call the `UIGraphicsBeginImageContextWithOptions` which creates a bitmap image context with the specified options. This is so we can create a new image. We need a context
5. the next 2 lines are helping to create this new image context
6. `CGContextSetRGBFillColor` and the next 2 lines set a number of options (fill color, blend mode, etc.) for the new image we are creating
7. the next line actually creates the image by calling `UIGraphicsGetImageFromCurrentImageContext`
8. then, with `UIGraphicsEndImageContext()` we remove the image context from memory
9. finally, we return our new image

Wow, lots of scary image-creation code here. The good news is that we don't really have to know exactly what each line does. For now, it's enough to know that this is the process we use to create a new iOS image object.

And more importantly, we need to do this so we can change the color of the **MessageBubble** image. So we're essentially creating a new iOS image object but based on the **MessageBubble** image. The only thing we are doing is changing its color.

Of course, we could always use a different image asset. Then we'd have one for each type of message. This is not a bad thing, it's actually quite common. I just wanted to show you an alternative way, working with a single asset. But we won't get into any of the graphics API because that is a course in and of itself. I still think it's cool to see it though.

Alright, let's take a breather and in the next lecture we will run the app to see if all this effort paid off.

01:10

In this lecture, we will run our app and see what we get.

* Run the app

Hey, look at that, we've been able to flip our image and change its color. We also changed the color of the other bubble to a nicer shade of blue so the text is more readable now.

![result](notes/4-appResult.png)

Section 7: UITableView Customization
02:31

In this lecture, we will stop the rows from highlighting when tapped.

So now we have our `UITableView` working. The bubbles are looking nice, each one facing the right direction. But when you tap a row, it highlights in grey.

This is OK, but we don't want it to do that. We want to keep our UI as clean as possible so let's remove the row's highlight.

This is a good trick to learn because the highlight is on by default and will often want to turn it off.

Open `ChatViewController.swift`.

We need to do 2 things for this to work. The first thing is to make our controller conform to the `UITableViewDelegate` protocol since that's the one responsible for handling the highlight.

With `UITableViews`, you'll find that some functionality is handled by the **Delegate** and some by the **Datasource**. In this case, it's one of the **Delegate** methods that we need to edit.

This means we first need to add the `extension` to our controller at the end of the class declaration (after the last closing curly brace), right below the Datasource declaration.

**Code:**

    extension ChatViewController: UITableViewDelegate{
        func tableView(tableView: UITableView, shouldHighlightRowAtIndexPath indexPath: NSIndexPath) -> Bool {
            return false
        }
    }

Delegate extension in context.

![Delegate](notes/1-delegate.png)

We added the Delegate extension and we also added the method responsible for highlighting the row, which is the `shouldHighlightRowAtIndexPath` method.

This method is quite simple, it returns a `boolean` indicating whether the row should be highlighted on tap. The default is true, so simply returning `false` from this method is enough to tell the `UITableView` not to highlight the rows when tapped.

If we were to run the app now, nothing would have changed. Try it if you want to see.

This is because we have added the Delegate and even the correct method to turn highlighting off, but we haven't told the controller who its delegate is!

Without this crucial piece of data, the controller can't call the delegate and our `shouldHighlightRowAtIndexPath` method will not run.

We'll do that in the next lecture.

00:44

In this lecture, we will add the `UITableView.delegate` variable and run our app to see the results.

We need to do this inside `viewDidLoad()`, right below the `.datasource` variable declaration.

**Code:**

    tableView.delegate = self

So the whole `viewDidLoad()` method looks like this:

![delegateVar](notes/2-delegateVariable.png)

Let's run the app now and see if our highlight is off.

* Run the app

Tap the rows, they should not highlight in grey.

![highlightOff](notes/2-highlightOff.png)

01:29

So things are looking better each time. But if you look closely, you'll see the bubble icon is a bit distorted. It's stretched because of the content.

I'll show you what I mean. In `ChatViewController.swift`, let's add a longer text to the messages.

Inside `viewDidLoad()`, comment out the `m.text = String(i)` line and let's add this new one.

**Code:**

    m.text = "This is a longer message. How does it look?"

It should end up looking like this:

![longer Text](notes/3-longerText.png)

* Run the app

Oh, that looks nasty.

![nasty](notes/3-nasty.png)

So what can we do to fix that? The answer is to use **edge insets**.

**Edge Insets** are part of the `UIKit` framework and they allow us to tell Xcode how stretch `UIViews`. This means we can tell Xcode which part of the `UIView` to **ignore** when stretching it.

This works really well for creating images with different contents based off a single image. The typical example for this is a UIButton with an image background that will resize according to contents.

We'll do the same thing here but with the bubble image instead of a button.

We will add the code for this in the next lecture.

03:01

In this lecture, we will fix the distortion of our bubble image.

In `ChatCell.swift`, locate the `makeBubble()` method. We'll be adding the new code in there.

First we need to declare the insets we want to use. Do this at the beginning of the method, right below this line:

    let image = UIImage(named: "MessageBubble")!

**Code:**

    let insetsIncoming = UIEdgeInsets(top: 17, left: 26.5, bottom: 17.5, right: 21)
    let insetsOutgoing = UIEdgeInsets(top: 17, left: 21, bottom: 17.5, right: 26.5)

We've simply declared 2 instances of `UIEdgeInsets`. One for the incoming bubble and one for the outgoing one.

The next thing we need to do is tell Xcode that our image is resizable and that we want to use the insets in it. We'll do this by **editing** the lines that declare outgoing and incoming images.

**Code:**

    //Make sure to locate the existing lines of code and update not add the following code: 

    let outgoing = coloredImage(image, red: 0/255, green: 122/255, blue: 255/255, alpha: 1).resizableImageWithCapInsets(insetsOutgoing)

    let incoming = coloredImage(flippedImage, red: 229/255, green: 229/255, blue: 229/255, alpha: 1).resizableImageWithCapInsets(insetsIncoming)

Our whole `makeBubble()` method now looks like this:

![makeBubble](notes/4-makeBubble.png)

The `resizableImageWithCapInsets` is the new code we added and what allows us to resize the image and use the Edge Insets.

* Run the app

![better bubbles](notes/4-betterBubbles.png)

Now that's much better. Now our image is no longer distorted and expands really nicely with the longer text.

We still have a problem with the text getting cut off and we will fix that later, but this is looking good. With a couple of lines of code we've been able to reuse the same image and stretch it dynamically based on content.

Let's make the text a little shorter so we can see both ends of the resized image.

In `ChatViewController.swift`, edit this line `m.text = "This is a longer message. How does it look?"` and shorten the text by deleting everything after the period.

**Code:**

    m.text = "This is a longer message."

* Run the app

Now we can see both ends of the image. Looking good!

![even better bubbles](notes/4-evenBetterBubbles.png)

04:23

In this lecture, we will constrain the bubble image so that it doesn't grow past the center of the screen in either direction.

To do this, we will create an array of constraints for the the outgoing and incoming images.

Let's start in `ChatCell.swift`.

We had these constraints declared before:

    private var outgoingConstraint: NSLayoutConstraint!
    private var incomingConstraint: NSLayoutConstraint!

We will replace them with arrays in the declaration.

**Code:**

    private var outgoingConstraints: [NSLayoutConstraint]!
    private var incomingConstraints: [NSLayoutConstraint]!

The reason we are doing this is so we can activate the constraints all at once. Check out our **Auto Layout** course for more on this.

The properties in context:

![array](notes/5-arrayConstraints.png)

Now, inside the `init` method, we will replace some of the constraints we had before and add the new ones into the array.

We'll need to **delete** these 3 constraints:

    bubbleImageView.topAnchor.constraintEqualToAnchor(contentView.topAnchor).active = true
    outgoingConstraint = bubbleImageView.trailingAnchor.constraintEqualToAnchor(contentView.trailingAnchor)
    incomingConstraint = bubbleImageView.leadingAnchor.constraintEqualToAnchor(contentView.leadingAnchor)

And let's add the new ones into the array.

**Code:**

      outgoingConstraints = [
        bubbleImageView.trailingAnchor.constraintEqualToAnchor(contentView.trailingAnchor),
        bubbleImageView.leadingAnchor.constraintGreaterThanOrEqualToAnchor(contentView.centerXAnchor)
      ]

    incomingConstraints = [
        bubbleImageView.leadingAnchor.constraintEqualToAnchor(contentView.leadingAnchor),
        bubbleImageView.trailingAnchor.constraintLessThanOrEqualToAnchor(contentView.centerXAnchor)
    ]

    bubbleImageView.topAnchor.constraintEqualToAnchor(contentView.topAnchor, constant: 10).active = true
    bubbleImageView.bottomAnchor.constraintEqualToAnchor(contentView.bottomAnchor, constant: -10).active = true

The complete `init` method now looks like this:

![init](notes/5-completeInitMethod.png)

In the next lecture, we will activate the constraints.

01:45

In this lecture, we will activate the constraints we created in the previous lecture.

Still in `ChatCell.swift`, find the `incoming()` method.

Before, we had the constraints as properties and activated them with `.active = true`. Now, they are in an array so we need to replace those lines.

Delete these 2 lines in the `if statement`:

    incomingConstraint.active = true
    outgoingConstraint.active = false

Replace them with these 2.

**Code:**

    NSLayoutConstraint.deactivateConstraints(outgoingConstraints)
    NSLayoutConstraint.activateConstraints(incomingConstraints)

We will do the same thing in the `else statement`. Delete these 2 lines:

    incomingConstraint.active = false
    outgoingConstraint.active = true

Replace with these 2 lines.

**Code:**

    NSLayoutConstraint.deactivateConstraints(incomingConstraints)
    NSLayoutConstraint.activateConstraints(outgoingConstraints)

Notice that we must deactivate before we activate.

The complete `incoming()` method:

![incoming](notes/6-completeIncomingMethod.png)

OK, time to view our handiwork.

* Run the app

Nice. Our bubbles don't stretch beyond the center of the screen.

But our text is getting cut off. We will use a quick fix to take care of this in the next lecture.

01:18

This will be a really short lecture. All we're going to do is use a quick trick to be able to see all our text inside the bubble.

Open `ChatViewController.swift`. Inside `viewDidLoad()`, right below `tableView.delegate = self`, let's add an estimated row height for the `UITableView`.

**Code:**

    tableView.estimatedRowHeight = 44

* Run the app

That's better:

![row height](notes/7-betterRowHeight.png)

We're telling Xcode to set the row height of each cell to 44 points.

By setting the `estimatedRowHeight` to any positive number, Xcode will dynamically adjust the height when it is drawing the rows. The closer the number is to the actual height, the less redraw Xcode has to do. Of course, when our text changes, there will have to be redrawing to accommodate the height.

01:46

Another short lecture. If we look at our app right now, it's shaping up really well.

![row height](notes/7-betterRowHeight.png)

But the cell separator, that grey line between bubbles, looks out of place in our design. It's a default feature of `UITableView` and it works for most cases, but not this one.

So, how can we get rid of it?

Easy, we will use the `separatorInset` property.

In truth, there are a couple of ways to remove this cell separator, if you search online you will probably find different suggestions on how to do it. They're all valid, but this one seems to be the simplest solution. Let's get to it.

In `ChatViewController.swift` inside the `cellForRowAtIndexPath` method, add this line.

**Code:**

     cell.separatorInset = UIEdgeInsetsMake(0, tableView.bounds.size.width, 0, 0)

![separator](notes/8-separatorInset.png)

Note that the `separatorInset` property takes an instance of `UIEdgeInset`, we can't just simply pass 0 to it. That's why we have to create the value using `UIEdgeInsetsMake`.

* Run the app

Great, the separators are gone.

![no separators](notes/8-noSeparator.png)

01:28

Yet another short lecture. We continue tweaking our UI. This is the way tweaks should be made, both in code and interface. You want to get used to making small adjustments and checking them.

It makes the whole process, not only, easier but also less work to debug. If something goes wrong, you know it was probably that last change you made. As opposed to making lots of changes across lots of files and then hunting down an elusive bug.

So, what we want to do in this lecture is add some height padding around our text.

Back in `ChatCell.swift`. We will change the height anchor of the bubbleView image. We will simply add a constant of 20 to this line:

    bubbleImageView.heightAnchor.constraintEqualToAnchor(messageLabel.heightAnchor).active = true

So it now becomes this line.

**Code:**

    bubbleImageView.heightAnchor.constraintEqualToAnchor(messageLabel.heightAnchor, constant:20).active = true

Adding the constant to the bubble image's height anchor will add padding to the text inside it.

* Run the app

That looks nicer.

![padding](notes/9-padding.png)

Section 8: Add Message Area
03:29

In this lecture, we will start creating a new message area.

In `ChatViewController.swift`, inside `viewDidLoad()`.

**Code:**

    let newMessageArea = UIView()
    newMessageArea.backgroundColor = UIColor.lightGrayColor()
    newMessageArea.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(newMessageArea)

We create a new `UIView`, change its background color, set the auto resize mask off and add the new view as a subview.

Now we will create the necessary constraints for this view and activate them.

**Code:**

    let messageAreaConstraints:[NSLayoutConstraint] = [
        newMessageArea.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor),
        newMessageArea.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor),
        newMessageArea.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor),
        newMessageArea.heightAnchor.constraintEqualToConstant(50)
    ]

    NSLayoutConstraint.activateConstraints(messageAreaConstraints)

The message area will sit at the bottom of our screen, so we'll need adjust the constraint for the `UITableView's` bottom to account for this new area. As such we'll need to edit this line:

    tableView.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor)

Into this line.

**Code:**

    tableView.bottomAnchor.constraintEqualToAnchor(newMessageArea.topAnchor)

So we're telling the `UITableView` that its bottom anchor is going to be the top of the new message area.

* Run the app

You should see the new message area as a gray bar at the bottom of the screen.

![message area](notes/1-messageArea.png)

03:06

In this lecture, we will create the `UITextView` and button for the new message area.

As its name implies, this area will be used for us to create and send messages.

Let's start by creating a property for the `UITextView` at the head of the `ChatViewController` class, right below the `UITableView` declaration: `private let tableView = UITableView()`.

**Code:**

    private let newMessageField = UITextView()

Now, inside `viewDidLoad()`, let's set up the `UITextView` and `UIButton`. We'll do this right below where we added the `newMessageArea` earlier: `view.addSubview(newMessageArea)`.

**Code:**

    newMessageField.translatesAutoresizingMaskIntoConstraints = false
    newMessageArea.addSubview(newMessageField)

    newMessageField.scrollEnabled = false

    let sendButton = UIButton()
    sendButton.translatesAutoresizingMaskIntoConstraints = false
    newMessageArea.addSubview(sendButton)

    sendButton.setTitle("Send", forState: .Normal)

    sendButton.setContentHuggingPriority(251, forAxis: .Horizontal)

We turn off auto resizing mask and add the view as usual.

Then we turn scrolling inside of the `UITextView` off. This is necessary because scrolling is enabled by default and we don't want that.

Next, we create an instance of `UIButton`, turn resizing mask off and add it to the view. We set the `UIButton's` title to "Send" and its `Content Hugging Priority` to 251 which is just above **Low Priority**. This will prevent the button from growing too much when using Auto layout.

If Content Hugging Priority is a new concept for you, make sure you check out the **Auto Layout** lectures.

In the next lecture, we will add constraints for all these elements and adjust the height constraint of the message area.

03:26

In this lecture, we will create add constraints for the new elements and adjust the height constraint of the message area.

Since we now have a `UITextView` inside the message area, we will remove the height constraint we set up earlier for it using a constant value. We will re-add the height constraint based on the `UITextView`.

So delete this line: `newMessageArea.heightAnchor.constraintEqualToConstant(50)`

And let's add the constraints. Make sure you add these to the existing array of constraints we already have.

**Code:**

    newMessageField.leadingAnchor.constraintEqualToAnchor(newMessageArea.leadingAnchor,constant:10),
    newMessageField.centerYAnchor.constraintEqualToAnchor(newMessageArea.centerYAnchor),
    sendButton.trailingAnchor.constraintEqualToAnchor(newMessageArea.trailingAnchor, constant:-10),
    newMessageField.trailingAnchor.constraintEqualToAnchor(sendButton.leadingAnchor,constant: -10),
    sendButton.centerYAnchor.constraintEqualToAnchor(newMessageField.centerYAnchor),
    newMessageArea.heightAnchor.constraintEqualToAnchor(newMessageField.heightAnchor, constant:20)

Constraints array in context:

![array](notes/3-constraintsArray.png)

Let's run the app and see what we got.

* Run the app

Hey, our message area now actually looks like a proper message area.

![area](notes/3-messageField.png)

Section 9: Animating Message Area
01:19

In this series of lectures, we will show how to make the message area move up when the keyboard is shown.

Right now, when we click the `UITextView`, and the keyboard comes up, the message area is covered by the keyboard.

This is what we need to fix. We need the message area to come up with the keyboard so that it rests right above it.

It will end up looking like this:

![moved](notes/1-messageAreaMoves.png)

To do this, we will remove the `newMessageArea` bottom constraint from the array of constraints we have and create a stored property for it, so that we can use it inside different methods.

We will then use `NSNotificationCenter` to let us know when the keyboard is being shown. We will create a method that gets called by the Notification and takes care of moving the message area as the keyboard slides up.

To get this effect, we will be animating a constraint!

Alright, so lots of interesting things in this series of lectures. Let's take it one step at a time and we'll explain the steps as we go along.

We will start in the next lecture.

02:19

In this series of lectures, we will modify the constraints so we can animate the necessary ones.

In `ChatViewController.swift`, let's add a store property (or instance variable) for the `newMessageArea`'s bottom constraint.

We will add it right below the array declaration, so right below this line: `private var messages = [Message]()`.

**Code:**

    private var bottomConstraint: NSLayoutConstraint!

We now want to remove the `newMessageArea`'s bottom constraint from the constraint of arrays we have inside `viewDidLoad()`. So find this line and delete it:

    newMessageArea.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor),

Delete the whole line, along with the comma. We will recreate this constraint but using the stored property we just declared.

Now let's recreate this constraint with the property. We will do this right above the declaration for our array of constraints.

**Code:**

    bottomConstraint = newMessageArea.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor)
    bottomConstraint.active = true

So we haven't done anything new here. We simply removed a constraint from inside the array and added it directly using a property.

Of course, since we activate all the constraints together inside the array, we need to activate this one directly. Very important we don't forget that or the constraint will not take effect.

That's it for this lecture. In the next lecture, we will start setting up the NSNotificationCenter listener.

03:08

In this lecture, we will add the `NSNotificationCenter` listener for the keyboard.

We will add this inside `viewDidLoad()`, right below where we activate the `tableViewConstraints`. So, right below this line: `        NSLayoutConstraint.activateConstraints(tableViewConstraints)`.

We will first add the code and then I'll explain what we're doing.

**Code:**

    NSNotificationCenter.defaultCenter().addObserver(self,
       selector: Selector("keyboardWillShow:"),
       name: UIKeyboardWillShowNotification,
       object: nil)

Let's first talk a little about `NSNotificationCenter`.

OK, so we know that an `NSNotificationCenter` object provides a mechanism for broadcasting information. We've used the radio analogy in the past. We said that a notification center is like a radio station, you tune in to the station you want to listen to.

In order to do this, we create an `observer`. Any object can actually be an `observer`.

We then need to specify a `Selector`, which will be the method that gets called when we receive a message from notification center.

We can specify the name for the notification so that we only listen to these particular notifications and not all notifications.

Lastly, there's an optional `object` parameter that we can use to filter even further the notifications. We can tell our observer to only listen for notifications from this particular object.

So let's look at the code above:

1. the `observer` is is set to `self` meaning the ChatViewController is the `observer`. This is typical when creating notifications.
2. the `selector` is `keyboardWillShow:`. This means we need to create this method ourselves to handle the notification. The colon `:` after the method name means we will be passing something to the method. In this case, we will be passing the notification itself.
3. the `name` is `UIKeyboardWillShowNotification`. This means we will only listen to notifications with this particular name.
4. `object` is nil, meaning we won't further filter the notifications.

Now the key here is the `UIKeyboardWillShowNotification`. This is part of the `UIKit` framework. It is a notification that is posted automatically by the system, right before the keyboard will be shown.

This is great for us, because we can simply listen for this notification and run whatever code we need to when the keyboard is shown.

Which is exactly what we will do in the next lecture.

07:38

In this lecture, we will create the method that gets called by the notification center and that actually animates the message area.

This method needs to be part of the `ChatViewController` so we'll create it right below the `didReceiveMemoryWarning()` method.

We will create the whole method and then I will explain what we're doing with each line.

This lecture will be a little long, but bear with me because I want to show the whole method in context.

**Code:**

    func keyboardWillShow(notification: NSNotification) {
        if let
            userInfo = notification.userInfo,
            frame = userInfo[UIKeyboardFrameEndUserInfoKey]?.CGRectValue,
            animationDuration = userInfo[UIKeyboardAnimationDurationUserInfoKey]?.doubleValue {
                let newFrame = view.convertRect(frame, fromView: (UIApplication.sharedApplication().delegate?.window)!)
                bottomConstraint.constant = newFrame.origin.y - CGRectGetHeight(view.frame)
                UIView.animateWithDuration(animationDuration, animations: {
                    self.view.layoutIfNeeded()
                })
        }
    }

This might look scary but it's not really. If we take it step by step, we'll see that everything makes sense.

1. This method takes an `NSNotification` as a parameter.
2. Then we're simply doing **Optional Chaining**, which means we're chaining multiple `if let`'s in a single declaration.
3. `userInfo` contains information about the notification. We will use it to get some information we'll need to create the animation.
4. With the information contained in `userInfo`, we set the `frame` constant to be the keyboard's `frame` as a `CGRect` type. We will use that information to convert coordinates between views.
5. The keyboard slides up in an animation handled by the system. But we don't know how long that animation takes. Yet we need to animate the bottom constraint of `newMessageArea` based on the keyboard animation. Fortunately, the notification contains that information, so we're asking `userInfo` for the animation duration right here: `userInfo[UIKeyboardAnimationDurationUserInfoKey]` and casting it as a `Double`. We need it as a double because we will use that value when we actually animate the `newMessageArea`.
6. Then we have the beginning of the `if let statement body` which is where we set up and animate the `newMessageArea`.
7. We start by setting `newFrame` to the frame of the keyboard window. We get the keyboard window's frame from here: `UIApplication.sharedApplication().delegate?.window)!`. `convertRect` is a method that converts coordinates from one view to another, we use to to make the calculations easier.
8. Next we change the `constant` property of the `bottomConstraint`, we set it to the vertical (`y`) position of `newFrame` minus the controller view's `frame`. So we're essentially getting the height of the keyboard.
9. Lastly, we call `animateWithDuration` which actually handles the animation. The `layoutIfNeeded()` method tells the system to redraw the layout if necessary.

So, you see, it's not really that bad once we take it step by step.

In the next lecture we will run the app and bask in our accomplishment.

01:03

Ok, let's see if our hard work has paid off.

* Run the app
* Click inside the UITextField to make the keyboard come up
* If the keyboard doesn't show in the Simulator, go to **Hardware-> keyboard-> Toggle Software keyboard** or simply **Cmd-K**

Nice. Our message area moves up with the keyboard and looks good doing it!

If you close the keyboard (**Cmd-K**) you'll see that the message area stays there floating, it doesn't move down with it. And we will take care of that in the upcoming lectures.

But right now, pat yourself in the back, you've just created a dynamic animation based on another animation. Very cool stuff.  

02:06

In this lecture, we will add a `UITapGestureRecognizer` to close the keyboard when the user taps anywhere outside of the keyboard area.

Right now, we don't really have a way of closing the keyboard. Let's make it so that the keyboard is closed whenever the user taps outside of it.

In order to do this, we will need to add a `UITapGestureRecognizer` and a method to handle the tap.

Let's start by adding the `UITapGestureRecognizer` inside `viewDidLoad()`, right at the bottom of the method.

**Code:**

    let tapRecognizer = UITapGestureRecognizer(target: self, action: "handleSingleTap:")
    tapRecognizer.numberOfTapsRequired = 1
    view.addGestureRecognizer(tapRecognizer)

First we create the `UITapGestureRecognizer` instance.

The `numberOfTapsRequired` is set to 1 by default, but it doesn't hurt to be explicit so we specify it.

Then we add the `UITapGestureRecognizer` we just created to the whole view in our controller. This way, a user can tap anywhere in the main view and the tap will be recognized.

In the next lecture, we will create the method to handle that tap.

01:33

In this lecture, we will add the method to handle the `UITapGestureRecognizer` we added previously.

In `ChatViewController.swift`, right below the `keyboardWillShow` method, let's add this new one.

**Code:**

    func handleSingleTap(recognizer: UITapGestureRecognizer) {
        view.endEditing(true)
    }

This method is very simple, all it does force the view to resign the **first responder status**. This means that the keyboard will close.

When the keyboard shows up, the `view` it is in will get a first responder status automatically. To close it, we simply resign this status and this is what we're doing in the method above.

* Run the app
* Tap on the `UITextView`

The keyboard will show and our message area moves up with it. We coded all this before.

* Tap anywhere in the main view other than the keyboard or message area

The keyboard will close. Perfect, this means that our `UITapGestureRecognizer` is working properly.

* Tap inside the `UITextView` again

Keyboard comes up again.

* Tap anywhere on the main view

The keyboard closes again. Our code works perfectly.

01:37

In this lecture, we will create another `NSNotificationCenter` `observer`.

We want the message area to come down with the keyboard. So we need to the opposite of the animation we created earlier. We have the message area moving up with the keyboard, but when the keyboard is closed, the message area remains floating in space. We need to fix that.

For this, we will create another `NSNotificationCenter` `observer`, another listener, if you will. So let's do that below the existing one.

**Code:**

    NSNotificationCenter.defaultCenter().addObserver(self,
       selector: Selector("keyboardWillHide:"),
       name: UIKeyboardWillHideNotification,
       object: nil)

This time, the name we will be listening for is `UIKeyboardWillHideNotification` and the method we will call is `keyboardWillHide`.

We will also move the code to handle the message area animation into a separate method, this way, we can call it from within both `keyboardWillHide` and `keyboardWillShow` methods.

We'll start with this in the next lecture.

01:34

In this lecture, we will move the animation for the message area into its own method.

First we will create the method to handle the animation. Later on we will create the method that the new observer calls. Then we will call this method from both observers.

The reason for this order will become apparent after we're done. OK, so right below the `handleSingleTap` method, let's create this one.

All we're doing is creating a new method called `updateBottomConstraint` and moving the **existing** code into that method. The code is now inside the `keyboardWillShow` method. We want to grab **all of the code** and move it into this new method we are creating.

So:

1. Create this new method.

**Code:**

    func updateBottomConstraint(notification: NSNotification) {

    }

2. Cut and paste all of the code inside `keyboardWillShow` into the body of `updateBottomConstraint`.

The new method will look like this:

**Code:**

    func updateBottomConstraint(notification: NSNotification) {
        if let
            userInfo = notification.userInfo,
            frame = userInfo[UIKeyboardFrameEndUserInfoKey]?.CGRectValue,
            animationDuration = userInfo[UIKeyboardAnimationDurationUserInfoKey]?.doubleValue {
                let newFrame = view.convertRect(frame, fromView: (UIApplication.sharedApplication().delegate?.window)!)
                bottomConstraint.constant = newFrame.origin.y - CGRectGetHeight(view.frame)
                UIView.animateWithDuration(animationDuration, animations: {
                    self.view.layoutIfNeeded()
                })
                tableView.scrollToBottom()
        }
    }

And the `keyboardWillShow` will be empty. We will take care of that in the next lecture.

02:52

In this lecture, we will add the necessary calls inside the various keyboard methods.

First things first. Earlier we left the `keyboardWillShow` method empty. Let's fix that.

We will add a call to the new `updateBottomConstraint` method inside it.

**Code:**

    func keyboardWillShow(notification: NSNotification) {
        updateBottomConstraint(notification)
    }

So, all this method does is call the `updateBottomConstraint` method.

We also need to create the `keyboardWillHide` method, which our new observer calls. This method will do the exact same thing as `keyboardWillShow`.

**Code:**

    func keyboardWillHide(notification: NSNotification) {
        updateBottomConstraint(notification)
    }

So you may be wondering, why do we have 2 distinct methods that do the exact same thing? The answer is simple:

each one is called from a different observer.

- `keyboardWillShow` is called when we receive a notification that the keyboard is about to be shown
- `keyboardWillHide` is called when we receive a notification that the keyboard is about to be hidden
- In both cases, we want to run the `updateBottomConstraint` method because that takes care of animating the message area

The other question is, why does the same code work for both moving the message area up or down? And this is the cool part:

Since we are basing our animation on the keyboard window, the same code will work for both cases.

This line: `let newFrame = view.convertRect(frame, fromView: (UIApplication.sharedApplication().delegate?.window)!)` gives use the `frame` of the keyboard window and we animate based on that. Our message area simply follows the keyboard window, whether it comes up or goes down.

See, I said that creating an animation based off another animation would be cool!

---

You might also be wondering why not call `updateBottomConstraint` from both observers, right? The reason is that we might want to add additional functionality when the keyboard shows up or goes away. Maybe we want to clear the `UITextView` when the keyboard is dismissed, for example. 

01:54

In this lecture, we will set the **Compression Resistance** for the `sendButton`.

When we created this button earlier, we set its **Content Hugging Priority** so that it wouldn't grow too much.

But we never set its **Compression Resistance** so that it wouldn't be squished. Let's do that now and then we will run the app with these lines commented out so you can see the difference.

Check out the **Auto Layout** lectures for more information on **Content Hugging** and **Compression Resistance**.

Right below `sendButton.setContentHuggingPriority(251, forAxis: .Horizontal)` add this line.

**Code:**

    sendButton.setContentCompressionResistancePriority(751, forAxis: .Horizontal)

We are setting the **Compression Resistance Priority** to `751` which is just above the default. This means our `UIButton` has less of a chance of getting squished.

* Run the app

Everything looks good.

* Now, comment out this line `sendButton.setContentHuggingPriority(251, forAxis: .Horizontal)`
* Run the app

Uh oh, our button has grown huge! And our `UITextView` is tiny!

![huge button](notes/11-hugeButton.png)

That's what Content Hugging is for, it will keep it from growing too much.

* Uncomment the line `sendButton.setContentHuggingPriority(251, forAxis: .Horizontal)`
* Run the app

Back to normal.

Section 10: Animating Message Area
02:21

In this series of lectures, we will add the ability to create a new message in our app.
Let's start by creating the pressedButton method so that something happens when we press the Send UIButton.
In ChatViewController.swift, right below updateBottomConstraint, let's create the pressedButton method.
Code:

func pressedSend(button:UIButton){
    guard let text = newMessageField.text where text.characters.count > 0 else {return}
    let message = Message()
    message.text = text
    message.incoming = false
    messages.append(message)
}

Let's see what this method does:

  1. First we make sure that the newMessageField has some text in it by counting the characters. If it doesn't, we return out of this method. No sense running unnecessary code if we don't have any text for the message.
  2. We then create an instance of Message().
  3. We set the text property of the instance to whatever text the user has inputted.
  4. Set the incoming property to false
  5. Finally, we append this message instance into the messages array.

So nothing crazy, pretty simple really.
But if you run the app now, nothing will happen when we press the Send button. This is because we have not told the UIButtonthat it needs to run this method when tapped. So, how do we do this?
We do this by adding a Target to the UIButton.
We'll do that next.

02:00

In this lecture, we will add a target to the Send UIButton
In viewDidLoad(), right below sendButton.setTitle("Send", forState: .Normal) let's add this line.
Code:

sendButton.addTarget(self, action: Selector("pressedSend:"), forControlEvents:.TouchUpInside)
###/precode###addTarget method can be tricky to read. Because you might think that the target for it should be the UIButton itself. But it's not. Here's how the method reads:
    ###li/li######li/li######licode###.TouchUpInside is the default for UIButtons###/olstrong###target is ###strong/strong###. In this case, we set it to self which means we are setting it to be the ChatViewController class. This class contains the pressedSend Selector that we just created so that works fine. We still need to do some more work before we can run the app. Right now, a new message gets added to the array, but we have nothing in place to allow us to show that new message. We don't even have a way to close the keyboard when we press the Sendbutton. We will take care of this next.
02:32

In this lecture, we will add the ability to show the new messages we create.
In order to show the new messages, we need to do 2 things.

  1. We need to reload the data into the UITableView
  2. We need to scroll the UITableView down so that new message is shown

We will do all this inside the pressedSend method. So at the bottom of the method.
Code:

tableView.reloadData()
tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: tableView.numberOfRowsInSection(0)-1, inSection: 0), atScrollPosition: .Bottom, animated: true)
###/precode###UITableView.
The second line scrolls down to this new message. We do this by calling numberOfRowsInSection to get the number of rows in theUITableView and then subtracting 1 from it.
Why do we subtract 1 from the count? This is because numberOfRowsInSection returns the count for all rows. But NSIndexPathfollows the array convention of starting with 0. So if we have 10 rows, the count is 10 but the index path for row 10 is actually 9.
UITableViews have sections by default. If we don't use them, as is the case here, we are in fact working in section 0 so we have to use that section in this method.
atScrollPosition is a constant. We can use .Top.Middle or .Bottom. We want to scroll to the bottom here so we use.Bottom.
  • Run the app
  • Type in a new message and press the Send button
Your message should show up at the bottom of the UITableView and it should scroll automatically to it.
03:14

In this lecture, we will move the code that scrolls to the bottom of the UITableView into its own function.
Doing this will allow us to call it from more than one place. We could simply copy and paste the code wherever we need it. But creating a function for it is a much better idea. You want your code to be modular and easy to edit. You don't want the same code repeated in different places, this makes it much more difficult to make changes in the code. It turns any change into an unnecessary treasure hunt.
Instead of creating a method inside the UITableView controller, we will create an extension to UITableView. This will allow us to reuse this helper in other UITableView controllers should we need to.
So we'll need to create a new file:

  • File-> New File-> iOS Source-> Swift File
  • Save As: UITableView+Scroll.swift

Code:

import Foundation
import UIKit

extension UITableView {
    func scrollToBottom() {
        self.scrollToRowAtIndexPath(NSIndexPath(forRow: self.numberOfRowsInSection(0)-1, inSection: 0), atScrollPosition: .Bottom, animated: true)
    }
}

Since we're creating this file by hand, we have to manually include the frameworks like Foundation and UIKit.
extension UITableView extends every UITableView we have in our app. This means that we'll be able to call thescrollToBottom function from within any UITableView we create.
Check out the Extensions Lectures for more on this.
We define a new function called scrollToBottom and then we copy/paste the code that we had in ChatViewController.swiftpressedSend() to have the UITableView scroll to the bottom.
Of course, we will need to delete that code from ChatViewController.swift. We'll do that next.

01:48

In this lecture, we will add the ability scroll to the bottom of the UITableView when the app starts.
Right now, when we start the app, we see the topmost rows of the table. Wouldn't it be nice if the app would start showing us the most recent messages? Of course it would. But these messages are located at the bottom of the table. So how do we accomplish this?
In ChatViewController.swift.
We will use the viewDidAppear() method for this. This method is similar to viewDidLoad() but it runs after that one.
viewDidLoad() runs when our view is loaded into memory. viewDidAppear() runs right after that, when the view actually appears.
Why don't we use viewDidLoad()? The reason is that we want the UITableView to have completely populated all its rows before we scroll. If we scroll too soon, the UITableView might still be drawing its content and we might not get all the rows.
Code:

override func viewDidAppear(animated: Bool) {        
    tableView.scrollToBottom()
}

Here we simply call the scrollToBottom method we created earlier, which will take care or scrolling the UITableView to the right place. This method will be called as soon as the ChatViewController view appears.
That takes care of scrolling to the bottom when we run the app. But we still need to fix scrolling to the bottom when we press theSend button.
We will do that next.

01:26

In this lecture, we will refactor the pressedSend method.
Right now, tapping the Send button will work because we have the correct code in it. But, since we have created a method to handle the scroll, we want to replace that code with the method call.
We will delete the line of code that scrolls the view inside pressedSend:

tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: tableView.numberOfRowsInSection(0)-1, inSection: 0), atScrollPosition: .Bottom, animated: true)

And replace it with this line.
Code:

tableView.scrollToBottom()

So the whole pressedSend method now looks like this:

func pressedSend(button:UIButton){
    guard let text = newMessageField.text where text.characters.count > 0 else {return}
    let message = Message()
    message.text = text
    message.incoming = false
    messages.append(message)
    tableView.reloadData()
    tableView.scrollToBottom()
}

Let's make sure all this works.

  • Run the app

It's a little hard to tell because all our messages say the same thing, but if you scroll the table, you'll notice that we are now at the bottom when the app starts.
Let's make sure our scroll works when we press the Send button.

  • Add a new message
  • Press the Send button

It should scroll again to the bottom.
Excellent. All is as it should be.

01:51

In this lecture, we will reset the UITextView when we send a new message.
Right now, when we press the Send button, the text of our new message is still shown in the UITextView.
The right thing to do would be to remove the text after the message has been sent. This is quite easy to do.
But the keyboard also stays onscreen after we send the new message. We want to dismiss the keyboard when we press send, we don't need it any more. This is also quite easy to do.
So, in ChatViewController.swift, in the pressedSend method, right above tableView.reloadData() add this line.
Code:

newMessageField.text = ""
###/precode###UITextView.
Then, at the bottom of the method, after this line tableView.scrollToBottom() add this code.
Code:
view.endEditing(true)
###/precode###view to resign its first responder status.
The whole pressedSend method.
func pressedSend(button:UIButton){
    guard let text = newMessageField.text where text.characters.count > 0 else {return}
    let message = Message()
    message.text = text
    message.incoming = false
    messages.append(message)
    newMessageField.text = ""
    tableView.reloadData()
    tableView.scrollToBottom()
    view.endEditing(true)
}
  • Run the app
  • Enter a new message
  • Press the Send button
Nice. Our keyboard disappears, our UITextView is reset, and we scroll down to the last message. This is starting to look like a real app!
01:28

In this lecture, we will make a small improvement to our app. We will trigger the scrollToBottom method only if we have rows in the UITableView.
This is another example of a pretty small detail that makes a difference. Except, this time, the difference is more for the developer than for the end user.
Let's see the code and then we'll talk more about it.
In UITableView+Scroll.swift, inside the scrollToBottom() method we have this line:

self.scrollToRowAtIndexPath(NSIndexPath(forRow: self.numberOfRowsInSection(0)-1, inSection: 0), atScrollPosition: .Bottom, animated: true)

We will put it inside of an if statement.
Code:

if self.numberOfRowsInSection(0) > 0 {
    self.scrollToRowAtIndexPath(NSIndexPath(forRow: self.numberOfRowsInSection(0)-1, inSection: 0), atScrollPosition: .Bottom, animated: true)
}

The whole method now looks like this:

  • Run the app

We want to make sure everything still works and we are not getting any errors. I know we made a simple change, but it's good to check our app continuously. Makes it easier to catch bugs this way, rather than having to come back after we've made many changes.
OK, so why have we done this?
We don't want the scrollToBottom() method to trigger unless there are some rows in our UITable. The user probably won't see a difference but it's not great programming. We're calling a function that is not really needed. If there are no rows in the table, then scrolling doesn't make sense, does it?
Let's try to get into the habit of only calling functionality when it's needed. This, not only, makes us better programmers, but also makes for a more robust app which will be easier to troubleshoot. Imagine if you had a bunch of methods that were called all the time, even when they are not needed and you're searching for a bug. Well, you would have to look at all that irrelevant code unnecessarily. Not a good use of your time.
And calling irrelevant code also makes our app slower since we're wasting cycles on something that does not produce any results.
So, in conclusion, only call the code that you need.

Section 11: Scroll to Bottom of Chat
01:17

Right now our app will attempt to scroll even if we have no messages. This might seem like a simple issue but we want to only run functionality when it is necessary.
Locate the scrollToBottom function in theUITableView+Scroll.swift extension. We will be adding a test to confirm that rows exist in our tableView before calling the existing scrollToRowAtIndexPath method.
Original Code:

self.scrollToRowAtIndexPath(NSIndexPath(forRow: self.numberOfRowsInSection(0)-1, inSection: 0), atScrollPosition: .Bottom, animated: true)

New Code with our if conditional:

if self.numberOfRowsInSection(0) > 0{
    self.scrollToRowAtIndexPath(NSIndexPath(forRow: self.numberOfRowsInSection(0)-1, inSection: 0), atScrollPosition: .Bottom, animated: true)
}

Our test uses the method numberOfRowsInSection takes one parameter. We use 0 since we only have 1 section. We confirm that the value returned is greater than 0. If and only if it is greater than 0 do we then scroll to the proper row.

Section 12: Add Timestamp to Model
00:45

In this series we want to time stamps to our messages. In order to accomplish this we will add an NSDate attribute to our Message Model.
Location of File:
Message.swift
Code:

var timestamp:NSDate?

Very simple lecture here. Adding the timestamp attribute will allow us to store an NSDate instance when we generate Message instances in the future.

03:04

It’s time to use our updated Message Model and add a timestamp to theMessage instances we already created.
Location:
We will be working in the ChatViewController in the viewDidLoadmethod.
Code:

var date = NSDate(timeIntervalSince1970: 1100000000) 

Here we create a simple NSDate instance. We’re using an arbitrary date, feel free to change it if you’d like to experiment with a different date.
Next in our for loop we update each message instance with our date instance.

m.timestamp = date

We access the timestamp attribute of the message we created in our last lecture and set it equal to our date.
Finally, we need to make sure that all of our dates are not the same so we will use a cool bit of code to change the date.

if i%2 == 0{
    //every other message will be a day later
    date = NSDate(timeInterval: 60 * 60 * 24, sinceDate: date)
}
###/precode###date variable so it will be the next day.
Location:
Now we’ll jump over to pressedSend method and also add the timestamp to the message.
Code:
message.timestamp = NSDate()
We set the message’s timestamp attribute to the current date usingNSDate()
01:39

We want to start grouping our messages by date. In order to do this we need to update our old messages Array into a Dictionary which will be able to store a group of messages for a specific date. We will also create a seperateArray to store all of the key dates for our new sections Dictionary. Lets see how this will work.
Original Code:

private var messages = [Message]()      

We delete this line of code so that we can replace it with a new Dictionary
New Code:

private var sections = [NSDate:[Message]]()
private var dates = [NSDate]()

Our new sections Dictionary has a Date as the key. We will use this key to store messages by date. Notice that I pluralized messages. That’s because messages is an Array. We will use the dates Array to access all of the keys for our sections Dictionary in the future.

05:15

The method we will create will take a single parameter, a message and it will handle the logic to appending the message. I’ll code out the function and then go over each step.
Location:
We will write this code in the ChatViewController.
Code:

func addMessage(message:Message){
    guard let date = message.timestamp else {return}
    let calendar = NSCalendar.currentCalendar()
    let startDay = calendar.startOfDayForDate(date)
    //we group messages by the day so we'll use the start of the day
}

We start by generating a method named addMessage. This method takes one parameter of type Message and we name it message.
Next we use a guard test. This ensures that message does in fact have a value for the timestamp. If it does not we exit the function.
To create a NSDate instance we need a NSCalendar instance so we generate one and name it calendar. There are a variety of calendars but we will stick with the default one.
We want to group our messages by day so we will use the start of the day. Luckily we have a method for this named startOfDayForDate and we can simply pass in our date that we got from our timestamp.
Now we want to add our date to our messages Array. Follow along with the code and i’ll explain after we finish writing it out.
Code:

var messages = sections[startDay]
if  messages == nil{
    dates.append(startDay)
    messages = [Message]()
}
messages!.append(message)
sections[startDay] = messages

Our messages array has been converted into a dictionary whose key is the date and the value is an array of messages. The first thing we want to do is get the correct value for the current startDay. We use the key startDay and store the returned array as a variable named messages.
Next, we check to see if the messages array is nil. If it is we append the startDay to the dates array. This is a seperate attribute we use to track all of the days we will display in our ChatViewController. We also initialize the messages array and specify that it will hold Message instances.
Finally, we append the new message to the messages Array and update the value for the startDay key to the new messages Array with the added message.
Now we can update our message with a new time stamp. And append it using the addMessage method.
Code:

addMessage(m)

We also want to update the way our messages are added in thepressedSend method.
Code:

addMessage(message)
01:36

Now that we have updated our messages Array into a sectionsDictionary we need to update the UITableView based on thisDictionary. In order to make this easy for us we will create a method which will return the messages for a given section. This will allow us to easily update our UITableViewDataSource methods in the following lectures.
Code:

func getMessages(section: Int)->[Message]{
    let date = dates[section]
    return sections[date]!
}
###/precode###Array of Message instances.
In the method we first get the date for the section by using our dates Array. We then return the correct Array of messages from our sectionsDictionary using the date as the key. Since this comes back as an optional we unwrap it with the ! symbol.
02:15

In this lecture we want to add and update our UITableViewDataSourcemethods to use the dates Dictionary since we replaced the messagesArray
Code:

func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return dates.count
}

We start by adding the number of sections in our UITableView with the count of the number of key-value pairs in our dates Dictionary.
Code:

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
    return getMessages(section).count
}

Next we update the numberOfRowsInSection method by using our recently created getMessages method. This returns an Array so we use the count attribute to return an Integer for this method.
Finally, we want to update the cellForRowAtIndexPath method.
Code:

let messages = getMessages(indexPath.section)

//At the end of our method

cell.backgroundColor = UIColor.clearColor()

Since we no longer have the messages Array attribute we use the getMessages method to get the messages for the given section. As a smallUser Interface we also update the cell’s background color to clear.

01:19

In this lecture we want to update the UITableView instance we setup as an attribute in the ChatViewController.
*Old code:

private let tableView = UITableView()       

*New Code:

private let tableView = UITableView(frame: CGRectZero, style: .Grouped)

Notice the attribute name has stayed the same UITableView. Onto the custom initialzer. For the first attribute we use CGRectZero which is the equivalent of CGRectMake(0,0,0,0). This would be 0 for the x, y, width and height. It’s just a default we use the tableView will still appear on the screen we just don’t need define its’ Rect. Using the .Grouped attribute will allow us to group our sections. This will look great as we start grouping our messages by date.

03:16

In this lecture we will add the viewForHeaderInSection method. This is part of the UITableViewDataSource extension so make sure to navigate there now!
Code:

func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    let view = UIView()
    view.backgroundColor = UIColor.clearColor()
    let paddingView = UIView()
    view.addSubview(paddingView)
    paddingView.translatesAutoresizingMaskIntoConstraints = false
}


This method specifies that we need to return a UIView optional. In order to show a header we need to create a UIView ourselves. We accomplish this by initializing a default instance of UIView. Next we set its’ background color to clear. We also want to some padding so we generate another UIViewnamed paddingView. Then we add add it as a subview to our view. Finally, we turn off the translates AutoResizingMaskIntoConstraintsattribute equal to false so it is ready to be used with autolayout.
We want to continue customizing this view so lets continue.
Code:

let dateLabel = UILabel()
paddingView.addSubview(dateLabel)
dateLabel.translatesAutoresizingMaskIntoConstraints = false
return view

We want to display a label on our paddingView so we initialize aUILabel. We then add it as a subview and set dateLabel’s 'translatesAutoresizingMaskIntoConstraints attribute to false. Pretty standard.
The method requires us to return a view so we return the view.
Hopefully this hasn’t been to confusing. We have just generated someUIElements in code and set them up in a UITableViewDataSource method which will automatically be called when the UITableView is being setup. This will allow us to have a header for each section in our tableView




03:29

In this lecture we will add some constraints to our paddingView and our dateLabel.

let constraints:[NSLayoutConstraint] = [
    paddingView.centerXAnchor.constraintEqualToAnchor(view.centerXAnchor),
    paddingView.centerYAnchor.constraintEqualToAnchor(view.centerYAnchor),
    dateLabel.centerXAnchor.constraintEqualToAnchor(paddingView.centerXAnchor),
    dateLabel.centerYAnchor.constraintEqualToAnchor(paddingView.centerYAnchor),
    paddingView.heightAnchor.constraintEqualToAnchor(dateLabel.heightAnchor, constant: 5),
    paddingView.widthAnchor.constraintEqualToAnchor(dateLabel.widthAnchor, constant: 10),
    view.heightAnchor.constraintEqualToAnchor(paddingView.heightAnchor)
]
NSLayoutConstraint.activateConstraints(constraints)

Our constraints center our paddingView and dateLabel in the viewand paddingView respectively. We also add some padding to our padding view as the name implies by adding a constant of 10 to the width and 5 and height anchors respectively. Finally, we specific the height of thepaddingView should be the same as the view.
Since we added our constraints as an Array we can activate them all at once!

03:10

In this lecture we want to setup a date formatter so we can properly display the date on our dateLabel and make our paddingView a bit more visually appealing.
Code:

let formatter = NSDateFormatter()
formatter.dateFormat = "MMM dd, YYYY"
dateLabel.text = formatter.stringFromDate(dates[section])

To start with the date we create an NSDateFormatter instance. We specify the format will be Month day and year. We then update the dateLabel’s text attribute using our formatter and the proper date key from the dates dictionary.
Next lets customize our paddingView.
Code:

paddingView.layer.cornerRadius = 10
paddingView.layer.masksToBounds = true
paddingView.backgroundColor = UIColor(red: 153/255, green: 204/255, blue: 255/255, alpha: 1.0)

We start by rounding the corners using the cornerRadius attribute. We use a value of 10 but if you’d like to play with different cornerRadius'sfeel free to play with this value. We set the masksToBounds attribute totrue. This will ensure that if the dateLabel or any other potential subviews of the paddingView will be clipped if they extend past thepaddingView's bounds.
Finally, we go ahead and give the paddingView a nice background color.
*Run the App

01:45

In order to make our UITableView look spiffy we will add 2 additionalUITableViewDataSource methods. These will create a UIView for our footer and then specify its’ height. This will give us a nice margin inbetween our sections.
Code:

func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
    return UIView()
}

For our Footer we do not need to add any text so we simply return a default UIView instance.
Code:

func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
    return 0.01
}

We set the height of our header to 0.01 so that the margin between our sections is very small. This is a minor stylistic change.

03:12

Once again we want to update our scrolling function to account for the changes we’ve made to our ChatViewController. Specifically, we’ve added the potential for multiple sections.
Location:
The method we want to update is located in the UITableView+Scroll.swift file.
Code:

if self.numberOfSections > 1{
    let lastSection = self.numberOfSections - 1
self.scrollToRowAtIndexPath(NSIndexPath(forRow:self.numberOfRowsInSection(lastSection) - 1, inSection: lastSection), atScrollPosition: .Bottom, animated: true)
} else if self.numberOfSections == 1 && self.numberOfRowsInSection(0) > 0 {
self.scrollToRowAtIndexPath(NSIndexPath(forRow: self.numberOfRowsInSection(0)-1, inSection: 0), atScrollPosition: .Bottom, animated: true)
}                 

Our update checks for the number of sections. Our first test accounts for multiple sections. In this case we determine what the last section is and then scroll to the last row in the last section.
If there is exactly 1 section and the number of rows in that section is greater than 1 we scroll the same way we have in the past.

Section 13: Core Data Mesage
02:00

In this series we will be integrating Core Data into our WhaleTalk application.
In this lecture we will add the CDHelper.Swift file. Make sure to download the file from the attachments. This is the same file we created together in theCore Data series.
Drag the CDHelper file into your project. Make sure to select the options:

  • Copy items if needed
  • Create groups
  • Add to targets WhaleTalk

If you setup WhaleTalk with Core Data selected makes sure to clear out the app delegate and use this version instead.
I want to point out that I have already adjusted a bit of the code to reflect the fact that we are in a project named WhaleTalk. If you have a different name please make the appropriate changes or your Core Data stack will not work!

03:13

Now that we have the CDHelper file added to our project we can setup our project to use Core Data.
Location:
We will start in the ChatViewController.swift file and add an attribute for the NSManagedObjectContext.
Code:

import CoreData


At the top of our file import the Core Data framework to get access to Core Data.
Code:

var context:NSManagedObjectContext?


Then we can add the context attribute of type NSManagedObjectContext. Notice that we make this an optional. This is because we do not setup the default value here. We will be doing that in the AppDelegate file.
Location:
We can now move to the App Delegate to finish our setup.
Code:

import CoreData


At the top of our file import the Core Data framework to get access to Core Data.
In the didFinishLaunchingWithOptions method lets setup the context for the ChatViewController.

let vc = window!.rootViewController as! ChatViewController
let context = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
context.persistentStoreCoordinator = CDHelper.sharedInstance.coordinator
vc.context = context


We’ve gone through this process before in the Core Data series; but lets go through a quick refresher. We start by getting access to theChatViewController instance we will display on the screen. We access this from the window attribute which knows what therootViewController is. Right now the rootViewController in our storyboard is the ChatViewController.


Next we setup our context. We use the .MainQueueConcurrencyType which is the default we have used in the past. We also setup the coordinator using the CDHelper file we added in our last lecture.


Finally, we update the context attribute of the ChatViewController class we named vc and set its’ context attribute equal to the context we just setup.





00:53

In this lecture we want to add a model named: Model.xcdatamodeld to our project so we can just the graphical tools to setup a `Message` Model.

To add our Model select File -> New -> File

Next we will choose `Core Data` from the left menu under the `iOS` section. 

Select `Data Model` and select next. 

Make sure to name it `Model`. This is the same as the name that I have setup for you in the `CDHelper`.

We now have a Model file where we can setup our Message Model in our following lectures.

02:08

In this lecture we want to create a Message Model and add 3 attributes.
Before we can do this lets make sure we don’t cause any issues by generating a file named Message since we already have a Message file. Delete the oldMessage file. Make sure to chose the option to Move to Trash. We will be replacing this with a NSManagedObject from our Model.
Navigate to the Model.xcdatamodeld file and press the Add Entitybutton at the bottom.
Select the entity name Entity and double click in to begin editing. Change the name to Message.
Now that we have an entity we can begin adding attributes. Press the +button in the attributes section. Change the first name to text and switch its’ type to String. Press the + again and add another attribute whose name should be incoming and type should be Boolean. Press the + a final time. Change the 3rd attributes name to timestamp and make the typeDate.
Now select the editor button from the top menus and then choose Create NSManagedObject Subclass from the dropdown menu. Make sure theModel is selected and press next. Make sure the Message is selected and press next. Finally, make sure the option to Use scalar properties as primitive data types is not selected.
The new Message+CoreDataProperties should have the following attributes.

@NSManaged var text: String?
@NSManaged var incoming: NSNumber?
@NSManaged var timestamp: NSDate?
01:58

In this lecture we will add a new attribute named isIncoming as well as a custom getter and setter to our isIncoming attribute in theMessage.swift file.
Location:
We will add this to the Message.swift file. We don’t add this to the extension because we want to be able to save this code incase we want to add additional attributes to the Message model in the future.
Code:

var isIncoming:Bool{
    get {
        guard let incoming = incoming else {return false}
        return incoming.boolValue
    }
    set(incoming) {
        self.incoming = incoming
    }
}

In this bit of code we first create a getter. If we request the isIncoming attribute we’ll’ test to see if the incoming attribute is nil using a guardtest. If it does not have a value we return false. If it has a value we return the incoming as a boolValue since Core Data store this as a NSNumber.
For the setter we simply take the incoming attribute and update the instance variable with this value. The real logic we needed to do was deal with the conversion of the NSNumber and the potential for a nil value.

02:47

In this lecture we’ll update the ChatViewController using the newMessage NSManagedObject subclass we setup.
To start with we’ll delete the default Message instances we setup earlier.
Code to be Deleted:
This is located inside of the viewDidLoad method.

var localIncoming = true
var date = NSDate(timeIntervalSince1970: 1100000000)

for i in 0...10 {
    let m = Message()
    m.text = "This is a longer message."

    m.incoming = localIncoming
    m.timestamp = date
    localIncoming = !localIncoming

    addMessage(m)

    if i%2 == 0{
        date = NSDate(timeInterval: 60 * 60 * 24, sinceDate: date)
    }
}

Next we will delete the old initialization of Message.
Original Code:

let message = Message()

We replace this by creating a Message instance using ourNSManagedObjectContext.
New Code:

guard let context = context else {return}
guard let message = NSEntityDescription.insertNewObjectForEntityForName("Message", inManagedObjectContext: context) as? Message else{return}

We use a guard statement to confirm that our context exists. We also use a guard test to make sure that our Message instance was in fact created.
After we can update the incoming attribute to the new attribute namedisIncoming.
Original Code:

message.incoming = false

New Code:

message.isIncoming = false

Finally, we’ll update the cellForRowAtIndexPath method to change theincoming attribute to isIncoming.
Original Code:

cell.incoming(message.incoming)

New Code:

cell.incoming(message.isIncoming)

In our next lecture we will save the Message instance to Core Data and retrieve our previously saved instances from Core Data.

03:26

Now we can use Core Data to both save and request saved Messages to populate our App.

do {
    let request = NSFetchRequest(entityName: "Message")
    if let result = try context?.executeFetchRequest(request) as? [Message] {
        for message in result{
            addMessage(message)
        }
    }
} catch {
    print("We couldn't fetch!")
}

We use a do catch test since executing our request is a throwing function. If we do get Messages back from Core Data we use the addMessagemethod to add them to our sections Dictionary.
Next we can save our Message instances in the pressedSend method.

do {
    try context.save()
}
catch {
    print("There was a problem saving")
    return
}

Notice that we once again use the do-catch test since save is also a ###code/code###. If there is an issue with saving we should see ###code/code### print to the console.

Section 14: Sort Dates and Messages
04:52

Right now our Messages when fetched come back unsorted. To make sense in a chat application we want update our request so it will sort our Messages by their time stamp.
Location:
ChatViewController.Swift file inside of the viewDidLoad method.
New Code:

request.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: false)]

We start by updating our request using an NSSortDescriptor. Using thetimeStamp key we specify that when we request our Messages we want them to be returned sorted by the attribute of timestamp. Core data will take care of the rest and return our Messages in the proper order.
Next we’ll make sure that are properly sorted.

dates = dates.sort({$0.earlierDate($1) == $0})

We’ll use the sort closure. We’re also using the short hand here. $0 repersents the first date argument and $1 repersents the second date argument. In order to compare the two dates we’ll use the method earlierDate which is defined in the NSDate class. The end goal is to figure out which of these two dates is earlier. == is just the equality comparator. 1 == 1 is true where as 2 == 1 is false. All we are doing is comparing numbers.
Though the sort and the NSSortDescriptor may seem redundant it’s also a safety check because it sorts messages that come in from Parse.
In case you are uncertain of what this should look like in context here it is:
Complete Method:

do {
    let request = NSFetchRequest(entityName: "Message")
    request.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: false)]
    if let result = try context?.executeFetchRequest(request) as? [Message] {
        for message in result{
            addMessage(message)
        }
        dates = dates.sort({$0.earlierDate($1) == $0})
    }
}

Lets confirm that the messages are now coming back in the proper order.

  • Run the app
Section 15: Fix Sorting
03:36

Right now we have a little bug. Lets first discover what this bug is.

**Run the Application**

Start by adding a few messages. Notice the order the messages are in. Rerun the application. You will see that the messages have been returned in the wrong order. Lets fix that problem.

**Location:**

ChatViewController.swift

**Code:**

To start off lets locate our first date sort. We will be moving this line of code so lets cut it using `Command and x`.

    dates = dates.sort({$0.earlierDate($1) == $0})

Lets readd this line of code into the if statement in the `addMessage` method right after we append our startDay to the datesArray.

    dates.sortInPlace{$0.earlierDate($1) == $0}

This won't fix our bug but it will make more sense from an organization perspective. We are keeping our sorting logic inside of the `addMessage` method so we can run all of these similiar operations in the same spot.

Now we can fix our bug. Sort the message after we complete our if statement right after we append our message to the messagesArray.

    messages!.sortInPlace{$0.timestamp!.earlierDate($1.timestamp!) == $0.timestamp!}

Rerun application and see the messages come back in the correct order.

Section 16: Add AllChatsViewController
00:33

Lets download the asset, new_chat, we will be using in this section. I have added it to my desk top.

Drag the file into the Assets.xcassets folder.

With the Image selected, change `Scale Factors` in the `Attributes Inspector` to `Single Vector`.

Drag the Image from where it is now in Unassigned to All Universal above it

03:04

Create a new Entity named `Chat`.

Attribute: lastMessageTime, Type: Date

Next we will setup a Relationship in the `Chat` model.

Relationship: messages, Destination: `Message`, Inverse: `chat`

In the Data Model inspector switch the Type to: `To Many`.

Now we can add the inverse relationship to the `Message` model.

Relationship: chat, Destination: `Chat`, Inverse: `messages`

Finally we need to change the Type. This is a one to many relationship so here we specify the Type as Many. 

Now lets generate the NSManagedObject. Editior - Create NSManagedObjectSubclass. Make sure you select both entities since we have made changes to both entities.

Do not check the option to use scalar properties for primitive data types. Delete the application from the simulator on the home screen. Rerun the application and make sure that you can build without a crash.

Now we want to add a computed property to our `Chat`.

**Location:**

`Chat.swift`

**Code:**

    var lastMessage:Message?{
        let request = NSFetchRequest(entityName: "Message")
        request.predicate = NSPredicate(format: "chat = %@", self)
        request.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: false)]
        request.fetchLimit = 1
        do{
            guard let results = try self.managedObjectContext?.executeFetchRequest(request) as? [Message] else {return nil}
            return results.first
        }catch{ print("Error for Request")}
        return nil
    }

This is a computed attribute that will allow us to quickly grab the last message for a given `Chat`. To complete this method we first setup an NSFetchRequest for the `Message` entity. We constrain this request with a predicate which specifies that we only want messages for the current `Chat` instance. We also sort the request by the `timestamp` attribute. Since we only want the last message we set the limit of returnable instances to one. Then we use a do-catch statement to many execute our request. We return the first result if the fetch is successful and return nil if it is not. This is possible because we made our type a `Message` optional.

04:37

Now we want to add a computed property to our `Chat`. This will allow us to easily determine the lastMessage for a given `Message` instance.

**Location:**

`Chat.swift`

**Code:**

    var lastMessage:Message?{
        let request = NSFetchRequest(entityName: "Message")
        request.predicate = NSPredicate(format: "chat = %@", self)
        request.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: false)]
        request.fetchLimit = 1
        do{
            guard let results = try self.managedObjectContext?.executeFetchRequest(request) as? [Message] else {return nil}
            return results.first
        }catch{ print("Error for Request")}
        return nil
    }

This is a computed attribute that will allow us to quickly grab the last message for a given `Chat`. To complete this method we first setup an NSFetchRequest for the `Message` entity. We constrain this request with a predicate which specifies that we only want messages for the current `Chat` instance. We also sort the request by the `timestamp` attribute. Since we only want the last message we set the limit of returnable instances to one. Then we use a do-catch statement to many execute our request. We return the first result if the fetch is successful and return nil if it is not. This is possible because we made our type a `Message` optional.

03:18

Got a simple lecture but easy to make a mistake here.

Rename the `ChatCell` to `MessageCell`. Make sure to change the name both in the file and the in the `Project Navigator`.

Now we need to recreate the `ChatCell`. Select File - New - File. Choose `iOS Source` and then `Cocoa Touch Subclass`. Make the file a subclass of `UITableViewCell` name it `ChatCell`.

Clear out the default code. We will add an initializer which accepts a `style` and a `reuseIdentifier`

Next we will add the initializer method.

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) { }

Notice that we use the override keyword since this method is defined in our super class.

We also need to add the required initializer. This is important to make our class NSCoding compatible. 

    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

It might be a bit confusing as to why we have to add this initializer but if we don't our computer program will break. If you'd like to read more about NSCoding check out this link: http://nshipster.com/nscoding/.

04:13

**Location:**

`ChatCell.swift`

**Code:**

We will begin setting up our ChatCell in this lecture. Lets start by adding some attribtues for UIElements we will be using for our UI.

    let nameLabel = UILabel()
    let messageLabel = UILabel()
    let dateLabel = UILabel()

Very straight forward. We create 3 UILabel attributes and initialize the UILabel.

Inside of the init method add the following lines of code:

    super.init(style: style, reuseIdentifier: reuseIdentifier)

    nameLabel.font = UIFont.systemFontOfSize(18, weight: UIFontWeightBold)
    messageLabel.textColor = UIColor.grayColor()
    dateLabel.textColor = UIColor.grayColor()

First, we call up to our super classes implementation of our initializer for setup purposes. Then we specify the font for our nameLabel and the textColor for our messageLabel and dateLabel.

    let labels = [nameLabel,messageLabel,dateLabel]
    for label in labels{
        label.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(label)
    }

To finish we create an array of labels and then use a for loop to iterate over our labels and set translatesAutoresizingMaskIntoConstraints to false. This way we are all ready to use `Auto Layout` in our next lecture!

03:55

In this lecture we will add autolayout to our labels so they will appear properly in our application.

**Location:**

`ChatCell.swift`

**Code:**

We will be adding our constraints in the `init` method.

    let constraints:[NSLayoutConstraint] = [
    nameLabel.topAnchor.constraintEqualToAnchor(contentView.layoutMarginsGuide.topAnchor),
    nameLabel.leadingAnchor.constraintEqualToAnchor(contentView.layoutMarginsGuide.leadingAnchor),
    messageLabel.bottomAnchor.constraintEqualToAnchor(contentView.layoutMarginsGuide.bottomAnchor),
    messageLabel.leadingAnchor.constraintEqualToAnchor(nameLabel.leadingAnchor),
    dateLabel.trailingAnchor.constraintEqualToAnchor(contentView.layoutMarginsGuide.trailingAnchor),
    dateLabel.firstBaselineAnchor.constraintEqualToAnchor(nameLabel.firstBaselineAnchor)
    ]
    NSLayoutConstraint.activateConstraints(constraints)

Our constraints accomplish the following:

* Constraining the nameLabel's Top and Leading Anchors to the contentView.
* Constraining the messageLabel's Bottom to the contentView. We constraint the Leading Anchor to the nameLabel above it.
* Constraining the dateLabel's TrailingAnchor to the contentView and the firstBaseLineAnchor to the nameLabel so the text will be aligned.

We complete the constraints by activating them. 

01:19

In this lecture we will register the MessageCell and make use of it.

**Location**

`ChatViewController.swift` 

**Code**

We will start by registering our cell for use in the viewDidLoad method. Since we already have a cell registered we will simply update the old line of code:

    tableView.registerClass(ChatCell.self, forCellReuseIdentifier: cellIdentifer)

All we have to do is replace `ChatCell` with `MessageCell`.

    tableView.registerClass(MessageCell.self, forCellReuseIdentifier: cellIdentifier)

The registerClass method allows the TableView to optimize using the MessageCell for reuse.

With the cell registered we can jump over the tableView `DataSource` method `cellForRowAtIndexPath`. Update the line of code where we create the cell.

    let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath:indexPath) as! MessageCell

Our error goes away for the incoming method. The `MessageCell` has this method!

01:02

Simple lecture here. We need to generate a new subclass of `UIViewController` named `AllChatsViewController`.

Select File - New - File. Choose `iOS Source` and then `Cocoa Touch Subclass`. Specify the name and subclass type.

Remove the boiler plate comments.

Importing the CoreData framework.

**Code:**

    import CoreData

02:15

Lets begin by adding some attributes to the `AllChatsViewController.swift`.

**Location:**

`AllChatsViewController.swift`

**Code:**

Lets start by adding an attribute of type NSManagedObjectContext. We make it an optional since we will be assigning the value from another class.

    var context:NSManagedObjectContext?

Next we create an attribute of type `NSFetchedResultsController`. We will be using a fetchedResultsController to fetch the data from `Core Data` and update our TableView.

    private var fetchedResultsController: NSFetchedResultsController?

Finally, we add attributes for our tableView and our cellIdentifier so we can properly reuse the `MessageCell`.

    private let tableView = UITableView(frame: CGRectZero, style: .Plain)
    private let cellIdentifier = "MessageCell"

04:08

We will do some more setup in this lecture for our ViewController to get up and running.

**Location:**

`AllChatsViewController.swift`

**Code:**

We will be writing our code in the `viewDidLoad` method.

    title = "Chats"

Set the title for the navigationBar equal to Chats.

Next we add a UIBarButtonItem to our navigationBar.

    navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(named: "new_chat"), style: .Plain, target: self, action: "newChat")

We use the UIBarButton's custom initializer to set it up with a image, style, a target and action.

Lets add the actions right away.

    func newChat(){
    }

Continuing in the viewDidLoad method. 

    automaticallyAdjustsScrollViewInsets = false

This bit will ensure that the navigationBar does not mess up our TableView by moving the cells improperly.

We next will do some setup for our tableView.

    tableView.registerClass(ChatCell.self, forCellReuseIdentifier: cellIdentifier)

    tableView.tableFooterView = UIView(frame: CGRectZero)

    tableView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(tableView)

We begin by registering the tableView to use the ChatCell. To use the tableView's footerView we have to initialize a UIView. We also want to be able to use `Auto Layout` on the tableView so we set translatesAutoresizingMaskIntoConstraints equal to false. Add the tableView to the view.

02:00

**Location:**

`AllChatsViewController.swift`

**Code:**

Lets add constraints in the `viewDidLoad` method.

    let tableViewConstraints:[NSLayoutConstraint] = [
    tableView.topAnchor.constraintEqualToAnchor(topLayoutGuide.bottomAnchor),
    tableView.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor),
    tableView.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor),
    tableView.bottomAnchor.constraintEqualToAnchor(bottomLayoutGuide.topAnchor)
    ]
    NSLayoutConstraint.activateConstraints(tableViewConstraints)

The constraints will stretch the tableView to the top, right, left and bottom of our app. We use the Layout Guides so the constraints will automatically account for our NavigationBar and down the road a TabBar. Finish by activiting the constraints.

03:14

Lets setup our fetchedResultsController for our ViewController.

**Location:**

`AllChatsViewController.swift`

**Code:**

In the viewDidLoad method we will begin our code.

    if let context = context{
        let request = NSFetchRequest(entityName: "Chat")
        request.sortDescriptors = [NSSortDescriptor(key: "lastMessageTime", ascending: false)]
        fetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
        do{
            try fetchedResultsController?.performFetch()
        }catch{
            print("There was a problem fetching.")
        }
    }

I will now explain what we just accomplished. We start by using an if let statement to safetly unwrap the context optional. If we have a context we can setup our NSFetchRequest for our `Chat` instances. We also want them to be sorted by the `lastMessageTime` attribute. With a valid `NSFetchRequest` we can setup a fetchedResultsController. Right now we don't need a sectionNameKeyPath or a cache so we set those attributes to nil. Finally, we use a `do catch` statement to attempt our fetch with our fetchedResultsController. If there is an issue we will print out to our console we had an issue with our fetch. 

01:39

In this lecture we will create a method to seed some fake Chat data into our application. Though we won't complete it we will begin outlining the method.

**Location:**

`AllChatsViewController.swift`

**Code:**

    func fakeData(){
        guard let context = context else {return}
        let chat = NSEntityDescription.insertNewObjectForEntityForName("Chat", inManagedObjectContext: context) as? Chat
    }

In this method we start by using a guard statement to confirm we have a valid context. Next we create a `Chat` instance by using the insertNewObjectForEntityForName method.

Call this method at the end of the `viewDidLoad` method.

    fakeData()

Students Who Viewed This Course Also Viewed

  • Loading
  • Loading
  • Loading

Instructor Biography

Mr. Eliot Arntz, Master iOS Teacher

Eliot regularly teaches iOS development classes and workshops at General Assembly and guest lectures for companies and development boot camps around NYC. He also taught the inaugural class for Coalition for Queens which focused on increasing diversity in iOS development. He also coaches students in a one-on-one environment with a focus on transitioning to full time development. Eliot cofounded and organizes the iOS Office Hours meetup NYC.

In his free time he works as a contractor for startups focusing on agile development.

Find me on twitter @EliotArntz - I check my direct messages quite frequently.

Instructor Biography

Mr. John Omar, Designer, developer, teacher at Bitfountain

John was the lead iOS developer at Fast Society and Cameo until he started Bitfountain in 2012. The apps that John has contributed to have appeared in TechCrunch, Mashable and the New York Times. At Bitfountain, John oversees all projects from a technical and strategic perspective. At Bitfountain, John has taught over 120,000 students how to code online.

Ready to start learning?
Take This Course