Back to blog

How To: Build A Real-Time Chat App With Firebase And Swift


Aasif Khan
By Aasif Khan | Last Updated on December 28th, 2021 6:42 am | 5-min read

Let’s build a chat app! In this guide, you’re going to build a chat app for iOS with Xcode 9, Swift 4 and Firebase. When you’re done, you can chat real-time with multiple users!

Firebase is perfect for building a chat app. It’s easy to use, can store a ton of data, and you can observe data changes in real-time! This app can serve as a foundation to build your own app, like iMessage, or you can follow along as a learning project.

Getting Started: Creating The Project In Xcode

Alright! Let’s get this party started…

The first thing you’re going to do is create a new Xcode project. That’s what you’d do for any new app.

So, start Xcode and choose File -> New -> Project…. A dialog opens:

This is where you typically choose an app project template, like a game, an augmented reality app, or an iMessage app.

Choose Single View App. This is a simple template with just one view controller, perfect for your chat app.

On the next screen, input the following items:

  • Product Name: Chat
  • Team: Your name or company name1
  • Organization Name: Your company name, like LearnAppMaking
  • Organization Identifier: A reverse-URL, like com.learnappmaking
  • Bundle Identifier: com.learnappmaking.Chat
  • Language: Swift, of course!
  • Leave the checkboxes for Core Data, Unit Tests and UI Tests unchecked

Your screen now looks similar to this:

Finally, click Next. Finally, in the next dialog, choose a convenient location to save your project. (I’m always saving my projects in a catch-all directory called Xcode Projects)

At last, the main interface of Xcode appears…

Look around – here’s what you see:

  • On the left, there’s a bunch of Navigators. They help with organizing your project, and they give access to important project assets and tools.
  • On the far right, there’s a bunch of Inspectors. They give you fine-grained control of aspects of your project, like UI elements in Interface Builder, Quick Help, and file properties.
  • In the middle you see the main editor, that currently shows Project Settings (above). When you’re coding, this area shows a text editor.
  • At the top left you see Play and Stop buttons, and a Target dropdown. With those buttons you can run and build your app, and you can choose on which device you want to run your app, like a physical iPhone, or an iPhone Simulator.
  • In the middle, at the top, you see a status bar. Important messages appear here.

OK, there’s a few things you need to change in your Xcode project.

  • First, open the ViewController.swift file by clicking on it, in the Project Navigator.
  • Then, at the top of the editor, find this code: class ViewController: UIViewController {.
  • Then, click on ViewController while holding the Command-key. A small popout appears – choose Rename…
  • Then, in the screen that appears, rename ViewController to ChatViewController. Make sure that the checkbox for Main.storyboard is ticked.
  • Finally, click Rename in the top-right to submit the changes.

Renaming files is a new feature, called refactoring, in Xcode 9. If you’re on Xcode 8, do this:

  • Rename ViewController to ChatViewController, simply by editing ViewController.swift
  • Open Main.storyboard, select Chat View Controller Scene in the list on the left, then open the Identity Inspector on the right (3rd tab), and make sure to set Class to ChatViewController

Next, you have to embed the chat view controller in a navigation controller. Do this:

  • First, open Main.storyboard by clicking on the file in the Project Navigator. Interface Builder opens…
  • Then, select Chat View Controller Scene in the list on the left. This will activate the main view controller you see in the editor, with a blue outline.
  • Then, choose from the menu Editor -> Embed In -> Navigation Controller. This will “wrap” the view controller in a navigation controller.

You can rearrange both view controllers in the editor, so that the navigation controller is placed on the left, and the chat view controller on the right.

You see an arrow going from the navigation controller to the chat view controller, indicating their connection. You can also spot a faint arrow on the left of the navigation controller, indicating that this is the first view controller of your app.

That’s all there is to it! You can try out your app by clicking the Play button, or by pressing Command-R. This will start your app in the iPhone Simulator. Your app should show up, and have an empty white UI with a navigation bar at the top.

You can also choose a different Simulator with the dropdown in the top-left corner (like iPhone 7).

Installing And Integrating CocoaPods

OK, before you can start coding there’s just one thing we have to do: installing CocoaPods.

Your project relies on third-party libraries:

  • JSQMessagesViewController, by Jesse Squires, a library for a chat UI
  • Firebase, from Google, a real-time NoSQL database

The easiest way to add third-party libraries to your project is with CocoaPods. CocoaPods is a package manager, and it helps you to manage your libraries, download new versions, and integrate them with Xcode.

If you haven’t installed CocoaPods before, you can do so by typing this on the command-line, in Terminal:

$ sudo gem install cocoapods

Never type in that $ – it’s used to indicate Command Line Interface (CLI)code. You can read more about working with CocoaPods in the Getting Started guide.

  • Next, when you’re in Xcode, right-click on your Chat project in the Project Navigator and choose New File….
  • Then, in the dialog that appears, scroll down, and choose the Empty template from the Other category.
  • Then, in the save dialog that appears, name the file Podfile and make sure to save the file in the same directory as Chat.xcodeproj. (Important!)

With Podfile open, add the following code in it:

platform :ios, ‘9.0’

target ‘Chat’ do
use_frameworks!

pod ‘Firebase/Core’
pod ‘Firebase/Storage’
pod ‘Firebase/Auth’
pod ‘Firebase/Database’
pod ‘JSQMessagesViewController’

end

Don’t forget to save the file!

With those lines you tell CocoaPods which pods it should install, for which platform and target. Easy-peasy!

Next, make sure to close your Xcode project by clicking the red (x) in the top-left corner of Xcode, and then close Xcode itself with Command-Q or by choosing Xcode -> Quit from the menu. (Important!)

Then, find your project’s folder in Finder. Use the Finder app to browse to the folder you save your project in, like Xcode Projects.

In case you already know how to work with Terminal, simply use cd to navigate to your project directory.

Then, open Terminal and type cd and then a space:

$ cd

Then, drag the Chat folder from Finder onto the Terminal window. This should paste the path of your project folder on the command-line. It’s a trick, so you don’t have to type in the entire path for your project folder, yourself.

Your Terminal window should now show something like:

$ cd /Users/username/Xcode\ Projects/Chat

Finally, press Enter.

Then, type in:

$ pod install

CocoaPods will now download all the pods for you, and generate a workspace that includes the chat project, and a new CocoaPods project. This CocoaPods project will build frameworks, and integrate those with your chat project – effectively integrating the third-party libraries!

Next up, with Finder still showing your project folder, make sure to open Chat.xcworkspace. Instead of working with the project, you’ll now work with the workspace. After all, your chat app project is now part of a workspace. (Important!)

Quick Tip: If you’ve opened the workspace and your projects appear “collapsed” or empty, then you forgot to close your project before you quit Xcode. Close all open projects and workspaces, and then open the chat workspace again. Solved!

In the screenshot below you can clearly see the work CocoaPods has done. On the left, in the Project Navigator, you see that there’s a new project: CocoaPods, that has added a bunch of code files and framework files.

If you open the Build Phases tab of your chat app’s Project Settings, you can see that CocoaPods has integrated the new pods with your project. CocoaPods essentially instructs Xcode to compile the third-party source code, and add it to your project as a framework.

You can even inspect the third-party code – check it out!

OK, there’s one thing left to integrate: a Bridging Header. One of the third-party libraries, JSQMessagesViewController, is written in Objective-C. Your project is coded in Swift, so you need to make a “connection” between the Objective-C library code and your own Swift code.

You can do so with a Bridging Header. It’s fairly easy to set up:

  • First, right click on your project in the Project Navigator and choose New File…
  • Then, select Header File from Source category and click Next.
  • Finally, in the save dialog, name the file Bridging-Header.h. Make sure to tick the checkbox at the bottom of the dialog, the one for Targets and Chat. Save the file alongside your Podfile and workspace files by clicking Create.

When the file opens, type the following in it:

#import

Then, do this:

  • First, go to your Project Settings by clicking on your project in the Project Navigator, and go to the Build Settings tab for the Chat Target.
  • Then, in the search box in the top-right, start typing bridging header. The editor filters its rows, and now locate the row that says Objective-C Bridging Header.
  • Finally, double-click on the empty column next to Objective-C Bridging Header – and an input box appears. Type in: Bridging-Header.h and click outside the input box to confirm your change.

Now check if everything still works by pressing Command-B. This will compile your project, and tell you if there are any errors. No errors? Great!

In case you get a compiler error like Error opening input file, you’ve most likely misspelled the bridging header file path, or saved the file in a different location. Check the location of your bridging header, and make sure that the Objective-C Bridging Header reflects this path, relative to your project folder. Chances are that Chat/Bridging-Header.h is the location you used…

Configuring Your Firebase Database

You’re now going to configure Firebase.

Firebase is a Platform-as-a-Service, providing cloud services for apps. With it, you can create a cloud database your app can connect to. Firebase also has an armada of other services, essentially covering the entire indie app landscape, with analytics tool, crash reporting, file storage and app monetization products.

Firebase is often compared with Parse Server, but it’s an entirely different tool, especially from a database perspective.

Firebase’s Database platform is flat, a “NoSQL”, and that means you can’t easily query for data, and can’t store relational data. Your database is flat, a tree-like structure, of which you can traverse paths.

Platforms like Parse Server, and the late Parse.com, are relational. You can create connections between objects in the database, called pointers, and you can execute complex queries with joins and where-clauses, similarly to how SQL works.

Firebase is especially suitable for real-time purposes, and for data that doesn’t need to be relational. Firebase is fast, and it has convenient tools for developers. For a chat app, Firebase is a perfect match.

The structure for our database is simple:

  • chatapp/
  • chats/
  • {$key}/
  • name (string)
  • sender_id (string)
  • text (string)

As you can see, the “root” of the tree-structure is chatapp. Chat messages are saved on the chats node. Every chat message has a unique $key, so you can uniquely identify each chat message. This $key is a special property of a Firebase database – you can compare it with a “unique key” in SQL.

Every chat message has three properties: a name, a sender_id and the chat text. The name is needed to show a chat user the sender of an incoming message, and the sender_id is used internally in the app

Sending a chat message is nothing more than appending message data to the chats node, with a unique $key, and receiving chat messages in the app is nothing more than observing data for the same chats node. A path like chatapp/chats/-KmLvuZfWAmvictV04_u/sender_id is called a reference.

This is what the data looks like in Firebase:

OK, now let’s configure Firebase! First, go to firebase.google.com and sign up for a free account. Then, log into your new account and go to your Firebase Dashboard.

Next, click Add Project.

In the dialogs that appear, choose the following settings:

  • Project Name: Chat
  • Country/Region: Choose your country or region

Finally, click Create Project. Then, in the screen that appears, choose Add Firebase to your iOS app.

Another dialog appears. Choose the following settings:

  • iOS Bundle ID: Input your app’s Bundle ID. You choose that when creating the Xcode project. It’s something like: com.learnappmaking.Chat.
  • App Nickname: Anything, something like “Chat App”

Finally, click Register App. In the next screen that appears, click the big Download GoogleService-Info.plist button. You’ll now download a .plist file, so save it in a convenient location (like ~/Downloads).

Do as the screen tells you, so add the .plist file to your Xcode project. Drag-and-drop the file from Finder into Xcode, and add it to the Project Navigator. When a dialog appears, make sure to tick the checkbox for Copy items if needed, and tick the checkbox next to Target.

Finally, click Continue. You can skip step 3, Add Firebase SDK, because you’ve already done that in this guide.

Continue to step 4, Add initialization code:

  • First, switch to Xcode and open AppDelegate.swift
  • Then, at the top of the file, right below import UIKit, type: import Firebase.
  • Finally, locate the function application(_:didFinishLaunchingWithOptions:) and add this line to the method: FirebaseApp.configure().

Try your code with Command-B. Are you getting any errors? Great!

In case you’re get an error that says Use of unresolved identifier “FirebaseApp”, change the line to: FIRApp.configure().

OK, now you’re going to do something else in Xcode. In order to work easier with the Firebase paths, and references, you create a Constants.swift file to hard-code the paths for the chat data.

Do this:

  • First, right-click on the project in Project Navigator and choose New File….
  • Then, pick the Swift File template, name the file Constants.swift, add it to the Chat Target, and save the file in your project folder.
  • Finally, click Create.

Then, add the following Swift code to Constants.swift:

import Firebase

struct Constants
{
struct refs
{
static let databaseRoot = Database.database().reference()
static let databaseChats = databaseRoot.child(“chats”)
}
}

The code above is a nested struct, a structure to store variables in. Whenever you now need access to the reference for chat data, you can use:

Constants.refs.databaseChats

You’re creating two static constants called databaseRoot and databaseChats. The root uses a function to get a reference to the root of the database, and then databaseChats “extends” that with a child node called chats.

OK, there’s one last thing we need to set straight: permissions. Firebase has an elaborate permissions tool, with which you can use a script-like language to grant and deny access to data, files, and so on.

Since your chat app is going to be a public free-for-all, and because no chat user will need to log in, the permissions for the chat app are simple:

{
“rules”: {
“.read”: true,
“.write”: true
}
}

Important: Never use the permission settings above in a production app! These settings effectively give anyone permission to read and edit any data (for this particular app). You’re using it now for education purposes, so remember to use a more sensible setting in your production Firebase apps. You can find more information here: Understand Firebase Realtime Database Rules.

To configure permissions in Firebase, do this:

  • First, go to your Firebase Dashboard and open your app. This will show you your app’s overview page.
  • Then, in the menu on the left, choose Database.
  • Then, top-left, choose the tab Rules.
  • Finally, replace the text in the editor with the new rules (sample above) and click Publish.

Make sure you input the new rules in the right menu – you can reach a similar-looking screen by choosing Storage -> Rules, for instance, but you need Database -> Rules!

Quickly check if your app still compiles and runs by pressing Command-R in Xcode. App shows up fine in iPhone Simulator? Neat! You can also check Firebase’s status messages in the Debug Window, bottom-middle of Xcode.

OK, that’s it. You’ve done enough configuring! It’s time to code Swift for real…

Setting Up The Messages View Controller

Let’s get to it!

You’re now going to create and configure the JSQMessagesViewController (JSQMVC). JSQMVC is a class that puts a list of message bubbles, much like iMessage, and a text input field on the app screen. It’s like a chat-app-UI-in-a-box.

With a little configuring, you’re going to integrate it in your app, and connect it to Firebase.

You’re starting with subclassing JSQMVC. Do this:

  • First, open ChatViewController.swift and locate the class definition at the top. This is the line that starts with class ….
  • Then, replace UIViewController with JSQMessagesViewController. An error should appear: Use of undeclared type.
  • Finally, add import JSQMessagesViewController below import UIKit. The error should now disappear.

Then, add the following two lines to the function viewDidLoad():

senderId = “1234”
senderDisplayName = “…”

You can add them right below super.viewDidLoad(). Make sure to change … to your own name!

If you then run your app by clicking the Play button or by pressing Command-R, you should see JSQMVC’s chat UI show up in iPhone Simulator, like this:

Configuring JSQMessagesViewController

OK, now you have to configure JSQMVC for a bit. This is what you’re going to do:

  • Add a local property to store messages in
  • Provide JSQMVC with the message data
  • Create message bubbles in the right colors
  • Hide avatars for message bubbles
  • Set the name label for message bubbles
  • Storing messages in messages

First, the local property to store messages. It’s an array, and you need it to display chat messages. In JSQMVC, those messages are shown with “bubbles”, much like iMessage. JSQMVC uses a collection view to display those chat bubbles, and it needs to be provided with the right data.

An array called messages is going to help with that. Add the following line to the top of the class:

var messages = [JSQMessage]()

You add this line after the opening squiggly bracket {, but before the function viewDidLoad(). That part of the code now looks like this:


class ChatViewController: JSQMessagesViewController
{
var messages = [JSQMessage]()

override func viewDidLoad()

That line declares and initializes a property – a variable that’s accessible throughout the entire class scope. It’s type is array of JSQMessage items, so it can store multiple objects of type JSQMessage.

You can access each item by an index number, from 0 to the end of the array. Arrays are a fundamental data structure in programming!

Providing JSQMVC with the message data

OK, next up: JSQMVC needs access to the data from messages in order to show those message bubbles.

Add the following two methods to the class:

override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageDataForItemAt indexPath: IndexPath!) -> JSQMessageData!
{
return messages[indexPath.item]
}

override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return messages.count
}

You can add them below the function viewDidLoad(), after the closing squiggly bracket } of that method, but before the closing squiggly bracket } of the class…

Quick Tip: See if you can spot all the opening and closing brackets {} in the above screenshot. It’s important to keep these brackets, and their indentation, intact – you’ll thank yourself later if you learn early on to properly indent and structure your code. Brackets are hard to spot when you make a mistake, so get comfortable with them!

Both classes override delegation methods. JSQMVC will call these methods when it needs them, so you provide the right function implementation, so you can customize JSQMVC to get data from the messages array.

The implementation of both methods is simple:

  • collectionView(_:messageDataForItemAt:) returns an item from messages based on the index from indexPath.item, effectively returning the message data for a particular message by its index.
  • collectionView(_:numberOfItemsInSection:) returns the total number of messages, based on messages.count – the amount of items in the array!

Create message bubbles in the right colors

Next up, the message bubbles and their colors. There are two colors for the messages, just like in the iMessage app:

  • Blue for outgoing messages, i.e. messages that the user sends
  • Gray for incoming messages, i.e. messages that the user receives

You can provide JSQMVC with a “bubble image data” object via another delegation method. You can create the image data from a factory method, which essentially creates the object for you based on a color you put in – quite literally, like a bubble factory!

First, add the following properties to the top of the class. Make sure to add them right below the message property, but before the viewDidLoad() method.

lazy var outgoingBubble: JSQMessagesBubbleImage = {
return JSQMessagesBubbleImageFactory()!.outgoingMessagesBubbleImage(with: UIColor.jsq_messageBubbleBlue())
}()

lazy var incomingBubble: JSQMessagesBubbleImage = {
return JSQMessagesBubbleImageFactory()!.incomingMessagesBubbleImage(with: UIColor.jsq_messageBubbleLightGray())
}()

Oooh, big chunk of complex code!

  • You’ve just created two properties called outgoingBubble and incomingBubble, both of type JSQMessagesBubbleImage.
  • Both properties are computed properties. This means that they don’t have a fixed value, like 42, but a computed value: the result of a function.
  • Both properties are lazy, which means that they’re only initialized once – when they’re accessed. When you access a lazy property for the first time, its value is initialized (“created”). For every subsequent access, the value of the property isn’t recreated (as for typical computed properties), but simply returns the initial value.
  • In Swift, typical computed properties can’t be lazy! You’ve coded around that by creating a closure. The closure’s result is the value for the lazy property, effectively creating a lazy computed property.

What’s the benefit? The bubble factory only creates bubbles once – saving resources.

Next, let’s put those properties to use. Add the following function to the class, below the other delegate methods:

override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAt indexPath: IndexPath!) -> JSQMessageBubbleImageDataSource!
{
return messages[indexPath.item].senderId == senderId ? outgoingBubble : incomingBubble
}

This function is yet another delegate, that’s called by JSQMVC when it needs bubble image data. Inside the function the ternary conditional operator a ? b : c is used to return the right bubble:

  • When messages[indexPath.item].senderId == senderId is true, return outgoingBubble
  • When that expression is false, return incomingBubble

Neat, right?

Hide avatars for message bubbles
OK, now let’s hide the avatars for message bubbles. JSQMVC can show them, but you don’t need that now.

First, add this function to the class, right below the other methods:

override func collectionView(_ collectionView: JSQMessagesCollectionView!, avatarImageDataForItemAt indexPath: IndexPath!) -> JSQMessageAvatarImageDataSource!
{
return nil
}

This function simply returns nil when JSQMVC wants avatar image data, effectively hiding the avatars.

Then, add the following code to viewDidLoad(), right below where you set the senderDisplayName and senderId:

inputToolbar.contentView.leftBarButtonItem = nil
collectionView.collectionViewLayout.incomingAvatarViewSize = CGSize.zero
collectionView.collectionViewLayout.outgoingAvatarViewSize = CGSize.zero

The first line hides the attachment button on the left of the chat text input field. The other two lines of code set the avatar size to zero, again, hiding it. Check your progress with this screenshot:

Set the name label for message bubbles

Lastly, let’s make sure the name label for the message bubbles is configured. This label shows up on top of the message bubble, and you can use it to display the name of the user that sent the chat message.

Add the following two methods to the class, right below the other methods:

override func collectionView(_ collectionView: JSQMessagesCollectionView!, attributedTextForMessageBubbleTopLabelAt indexPath: IndexPath!) -> NSAttributedString!
{
return messages[indexPath.item].senderId == senderId ? nil : NSAttributedString(string: messages[indexPath.item].senderDisplayName)
}

override func collectionView(_ collectionView: JSQMessagesCollectionView!, layout collectionViewLayout: JSQMessagesCollectionViewFlowLayout!, heightForMessageBubbleTopLabelAt indexPath: IndexPath!) -> CGFloat
{
return messages[indexPath.item].senderId == senderId ? 0 : 15
}

Again, these two delegate methods are called when JSQMVC needs information:

  • collectionView(_:attributedTextForMessageBubbleTopLabelAt:) is called when the label text is needed
  • collectionView(_:collectionViewLayout:heightForMessageBubbleTopLabelAt:) is called when the height of the top label is needed

Inside the methods, you see a similar a ? b : c code, like you used before, to determine if the message is sent by the current user, or sent by someone else. Logically, if the current user sent the message, you don’t have to show their sender name, so the label stays empty and hidden.

In the code you can also see messages[indexPath.item].senderDisplayName.

This code accesses a particular message by index, and then uses the property senderDisplayName on the resulting object. The type of this object is JSQMessage, as defined in property messages, so that’s why you can use the property on that message object.

Pfew! That’s quite a bit of code… Ready to finally write some interactive, message-sending code?

Quick Tip: Every time you make significant changes to your project’s code, either build (Cmd-B) or run (Cmd-R) your app to make sure you haven’t made any mistakes. If errors show up, fix them before you move on.

Sending A Chat Message With didPressSend()

At this point all you’ve done is configuring and preparing, but now you’re ready to actually send chat messages. And that’s surprisingly simple!

When the user has typed a chat message, and then presses the Send button, in the app, you will respond to that action by sending data to Firebase.

Add the following function to the ChatViewController class, right below the other methods:

override func didPressSend(_ button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: Date!)
{
let ref = Constants.refs.databaseChats.childByAutoId()

let message = [“sender_id”: senderId, “name”: senderDisplayName, “text”: text]

ref.setValue(message)

finishSendingMessage()
}

This function overrides a function on JSQMessagesViewController, the class you subclassed. This enables you to override what happens when a user presses the Send button, effectively creating your own implementation.

So… what happens?

  1. You create a reference to a new value, in Firebase, on the /chats node, using childByAutoId()
  2. You create a dictionary called message that contains all the information about the to-be-sent message: sender ID, display name, and chat text
  3. You set the reference to the value – you store the dictionary in the newly created node
  4. Finally, you call finishSendingMessage(), a function that tells JSQMVC you’re done

The data for message is provided via the function parameters, so JSQMVC tells you what the user typed in the chat field.

That childByAutoId() function is interesting. It’s part of Firebase, and it can be used to generate a unique key.

In the code above, you’re creating a new chat message. You designed previously to store all the messages on the path /chats/. The function childByAutoId() will generate a new unique key on that path, so you can attach data to it.

An example path is /chats/-KmLvuZfWAmvictV04_u.

You can see it as a tree. The tree starts at the root, and then branches out. One branch is /chats, that sub-branches into a new branch – the one generated with childByAutoId().

You need those randomly generated keys, otherwise you’d end up with a conflict: what if 3 users all named their branch “message42”?

Then, at point 3, you assign a value to that path, like “branch = value”. It had no value before (“null”) and with setValue(_:) you’ve given it a value.

But… here’s the kicker – the values are branches too! That’s the beauty of Firebase: it’s all one big tree structure. Like this:

For instance, the value of path chatapp/chats/-KmLtIPCrC-wYSwGg8nX/text is Do you take the red or the blue pill, Neo?. Awesome!

Why don’t you try it out for yourself?

  • Run your chat app with Command-R
  • Type in a chat message and press Send
  • Open Firebase, and go to Database -> Data
  • Your message data is shown there!

Observing Firebase For New Chat Messages

Your chat app is close to coming together! Let’s add the most critical piece of code: observing Firebase data.

One of the grea things Firebase can do is push real-time data to your app, with very little effort or code. It’s literally real-time: you change your data in Firebase, and your UI or data on your iPhone changes. Perfect for a chat app!

First, add the following code to the end of viewDidLoad(). Make sure to add it within the methods closing bracket }, right below the collectionView … lines.

let query = Constants.refs.databaseChats.queryLimited(toLast: 10)

_ = query.observe(.childAdded, with: { [weak self] snapshot in

if let data = snapshot.value as? [String: String],
let id = data[“sender_id”],
let name = data[“name”],
let text = data[“text”],
!text.isEmpty
{
if let message = JSQMessage(senderId: id, displayName: name, text: text)
{
self?.messages.append(message)

self?.finishReceivingMessage()
}
}
})

The code looks badass, but it’s actually quite simple. Here’s what happens:

  1. Create a query to get the last 10 chat messages
  2. Observe that query for newly added chat data, and call a closure when there’s new data
  3. Inside the closure the data is “unpacked”, a new JSQMessage object is created, and added to the end of the messages array
  4. The function finishReceivingMessage() is called, which prompts JSQMVC to refresh the UI and show the new message

Let’s take a closer look at the lines of codes. First, this one:

let query = Constants.refs.databaseChats.queryLimited(toLast: 10)

This declares and initializes a constant called query. It’s assigned the result of queryLimited(toLast: 10). This function is called on databaseChats, the reference you created earlier in Constants.swift. This line of code effectively prepares a query: an instruction for the database which data to retrieve.

Then, in this part of the code, the actual retrieving happens:

_ = query.observe(.childAdded, with: { [weak self] snapshot in

The function observe(_:with:) is called on the query. The Firebase framework now starts “observing” the query for changes. Whenever a new object in Firebase is created, the closure is called.

A closure is like a function, but then different. You can use closures to pass around blocks of code, as if they’re variables, but you can also invoke those blocks of code, as if they’re functions.

You’ve probably guessed by now that there’s some interaction between didPressSend() and observe(_:with:). When a new chat message is typed and sent, it’s returned via the observer function, and shown on screen. (That’s why you didn’t see chat messages before!)

Then, next is that set of if let code:

if let data = snapshot.value as? [String: String],
let id = data[“sender_id”],
let name = data[“name”],
let text = data[“text”],
!text.isEmpty

The if let statement uses optional binding to unwrap and cast snapshot to a dictionary of strings. Subsequent lines then assign values of that dictionary to constants.

The last line of code checks if text isn’t empty, and if it is, the block of code below it (between the { }) doesn’t execute. This makes sure that no empty text bubbles show in the UI.

Finally, this block of code:

if let message = JSQMessage(senderId: id, displayName: name, text: text)
{
self?.messages.append(message)

self?.finishReceivingMessage()
}

With it, a new JSQMessage object is created. It’s provided with the id, name and text from the snapshot – the data that’s returned from Firebase.

The if let conditional binding statement is needed because JSQMessage can return an optional, so it needs to be unwrapped.

Finally, the newly incoming message is appended to the messages array. When finishReceivingMessage() is called, JSQMVC updates the UI and shows the new message bubbles.

Try it! Is it working?

Setting A User’s Display Name With A Dialog

At this point the app is almost done, but one thing is missing. It’s not ready for multiple users!

Because senderId and senderDisplayName are hard-coded to two default values, all installs of this app would show up as the same user…

Let’s change that!

Add the following function below viewDidLoad(), so right after the closing squiggly bracket }, but before the function collectionView(_:messageDataForItemAt:).

@objc func showDisplayNameDialog()
{
let defaults = UserDefaults.standard

let alert = UIAlertController(title: “Your Display Name”, message: “Before you can chat, please choose a display name. Others will see this name when you send chat messages. You can change your display name again by tapping the navigation bar.”, preferredStyle: .alert)

alert.addTextField { textField in

if let name = defaults.string(forKey: “jsq_name”)
{
textField.text = name
}
else
{
let names = [“Ford”, “Arthur”, “Zaphod”, “Trillian”, “Slartibartfast”, “Humma Kavula”, “Deep Thought”]
textField.text = names[Int(arc4random_uniform(UInt32(names.count)))]
}
}

alert.addAction(UIAlertAction(title: “OK”, style: .default, handler: { [weak self, weak alert] _ in

if let textField = alert?.textFields?[0], !textField.text!.isEmpty {

self?.senderDisplayName = textField.text

self?.title = “Chat: \(self!.senderDisplayName!)”

defaults.set(textField.text, forKey: “jsq_name”)
defaults.synchronize()
}
}))

present(alert, animated: true, completion: nil)
}

Big function! What does it do?

  • First, create an alert controller. With it, you can display an alert dialog box on screen. Provide it with a title and a message.
  • Then, add a text field to the alert dialog. The text field is either provided with a value from UserDefaults, or with a random item from the array names.
  • Then, add an action to the alert dialog. When the user taps “OK”, the closure is executed. In the closure, the sender display name is changed, as well as the view controller title, and the new name is stored in UserDefaults.
  • Finally, the alert dialog is presented on screen.

But… what’s it really do? Well, the dialog asks you for a new sender display name. With it, you can determine your chat username! The alert dialog saves your chosen username to UserDefaults, a special kind of storage for app settings.

Quick Note: If you’re on Swift 3 / Xcode 8, remove the @objc in the function definition.

Next, replace this code in viewDidLoad()

senderId = “1234”
senderDisplayName = “[yourname]”

With this:

let defaults = UserDefaults.standard

if let id = defaults.string(forKey: “jsq_id”),
let name = defaults.string(forKey: “jsq_name”)
{
senderId = id
senderDisplayName = name
}
else
{
senderId = String(arc4random_uniform(999999))
senderDisplayName = “”

defaults.set(senderId, forKey: “jsq_id”)
defaults.synchronize()

showDisplayNameDialog()
}

title = “Chat: \(senderDisplayName!)”

let tapGesture = UITapGestureRecognizer(target: self, action: #selector(showDisplayNameDialog))
tapGesture.numberOfTapsRequired = 1

navigationController?.navigationBar.addGestureRecognizer(tapGesture)

The code does this:

  • First, create a temporary constant for the standard UserDefaults
  • Then, check if the keys jsq_id and jsq_name exist in the user defaults.
  • If they exist:
  • Assign the found id and name to senderId and senderDisplayName
  • If they don’t exist:
  • Assign a random numeric string to senderId and assign an empty string to senderDisplayName
  • Save the new senderId in the user defaults, for key jsq_id and save the user defaults (with synchronize())
  • Show the display name alert dialog (the one you coded earlier)
  • Then, change the view controller title to “Chat: [display name]”
  • Finally, create a gesture recognizer that calls the function showDisplayNameDialog when the user taps the navigation bar.

The code above effectively checks whether a display name and sender ID were previously set. If they weren’t, it generates a random sender ID, and gives the user the opportunity to set a display name.

The user can also change their display name at any point by tapping the navigation bar.

So, one of two scenarios can happen:

  • When a user starts the chat app for the first time, they get a random sender ID, and they’re asked to input a sender display name
  • When a user starts the app for a subsequent time, the app uses their previously set sender ID and display name. The user can optionally change their display name at a later point by tapping the navigation bar.

Conclusion

And… that’s all there is to it! Fire up iPhone Simulator, run your app, and start an interactive chat session.

You can open multiple simulators in Xcode 9 by choosing Hardware -> Device -> iOS in iPhone Simulator, and picking a different device than you’re currently running. Next, run the app in Xcode for that device, and restart the app on your current device. Result? Two apps on two iPhones!


Aasif Khan

Head of SEO at Appy Pie

App Builder

Most Popular Posts