Advanced iOS Instruction: Clone WhatsApp with Bitfountain
4.0 (80 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.
597 students enrolled
Wishlisted Wishlist

Please confirm that you want to add Advanced iOS Instruction: Clone WhatsApp with Bitfountain to your Wishlist.

Add to Wishlist

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.0 (80 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.
597 students enrolled
Last updated 4/2016
English
Current price: $10 Original price: $50 Discount: 80% off
1 day left at this price!
30-Day Money-Back Guarantee
Includes:
  • 13 hours on-demand video
  • 10 Supplemental Resources
  • Full lifetime access
  • Access on mobile and TV
  • Certificate of Completion
Have a coupon?
What Will I Learn?
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.
View Curriculum
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.
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.


Who is the target audience?
  • Anyone interested in learning how to build a complex app from beginning to end.
Students Who Viewed This Course Also Viewed
Curriculum For This Course
Expand All 289 Lectures Collapse All 289 Lectures 12:58:08
+
Intro
1 Lecture 01:27

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

Preview 01:27
+
Setup
2 Lectures 06:59

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.

Preview 02:29

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)

Preview 04:30
+
Add Data Source
3 Lectures 10:30

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.

Preview 02:26

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.

WhaleTalk Add Fake Data
02:44

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.

WhaleTalk Register Cell and Add Datasource
05:20
+
Add Cell Model & Bubble for Message
5 Lectures 19:37

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. 

Add Cell Model
04:34

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.

Configure Cell Model
08:07

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.

Finish Configuring the Cell
01:58

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.

Add Image Asset
02:32

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.

Edit ChatViewController to Use New Cell
02:26
+
Incoming Messages
3 Lectures 06:37

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.

WhaleTalk Incoming Messages
02:36

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.

Add New Constraints Properties
01:49

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. 

Function to Activate Constraints
02:12
+
Flip Bubble & Change Color
4 Lectures 14:31

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.

Change Image Color
01:49

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.  

Make Bubble
05:06

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.

coloredImage Function
06:26

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)

Run the App
01:10
+
UITableView Customization
9 Lectures 18:25

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.

UITableView Stop Rows Highlighting
02:31

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)

UITableView Add Delegate Variable
00:44

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.

Distorted Bubble
01:29

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)

Fixing Distorted Bubble
03:01

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.

Constraint Bubble
04:23

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.

Activate Constraints
01:45

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.

Increase Row Height
01:18

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)

Remove Cell Separator
01:46

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)

Pad Text
01:28
+
Add Message Area
3 Lectures 10:01

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)

Add Message Area Constraints
03:29

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.

Add Message Field
03:06

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)

Add Constraints to UITextView and UIButton
03:26
+
Animating Message Area
11 Lectures 27:03

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.

Moving Message Area Intro
01: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.

Modifying Constraints
02:19

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.

Adding NSNotfication Listener
03:08

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.

Animate Message Area
07:38

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.  

Run the App
01:03

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.

Adding UITapGestureRecognizer
02:06

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.

Add Method to Handle Tap
01:33

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.

Add New Observer
01:37

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.

Add Method to Handle Animation
01:34

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. 

Adding Proper Calls Into Keyboard Methods
02:52

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.

Set Compression Resistance for sendButton
01:54
+
Animating Message Area
8 Lectures 16:40

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.

Create pressedButton Method
02:21

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.
Add Target to UIButton
02:00

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.
Show New Message
02:32

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.

Create scrollToBottom Function
03:14

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.

Scroll to Bottom on Start
01:48

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.

Refactor pressedSend Method
01:26

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!
Reset UITextView on Send
01:51

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.

Scroll to Bottom on Keyboard Show
01:28
77 More Sections
About the Instructor
Mr. Eliot Arntz
4.0 Average rating
93 Reviews
680 Students
4 Courses
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.

Mr. John Omar
4.4 Average rating
239 Reviews
16,596 Students
7 Courses
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.