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.
Ready to get started? Here’s a quick overview of the steps:
Last Updated: June 25, 2018. Updated for Swift 4.0, and the code is compatible with Swift 5.
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:
LearnAppMaking
com.learnappmaking
com.learnappmaking.Chat
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:
OK, there’s a few things you need to change in your Xcode project.
ViewController.swift
file by clicking on it, in the Project Navigator.class ViewController: UIViewController {
.ViewController
while holding the Command
-key. A small popout appears – choose Rename...
ViewController
to ChatViewController
. Make sure that the checkbox for Main.storyboard
is ticked.Renaming files is a new feature, called refactoring, in Xcode 9. If you’re on Xcode 8, do this:
ViewController
to ChatViewController
, simply by editing ViewController.swift
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:
Main.storyboard
by clicking on the file in the Project Navigator. Interface Builder opens…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).
OK, before you can start coding there’s just one thing we have to do: installing CocoaPods.
Your project relies on third-party libraries:
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.
New 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:
New 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 <JSQMessagesViewController/JSQMessages.h>
Then, do this:
bridging header
. The editor filters its rows, and now locate the row that says Objective-C Bridging Header
.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…
Done and done! Let’s continue…
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}/
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:
Chat
Finally, click Create Project. Then, in the screen that appears, choose Add Firebase to your iOS app.
Another dialog appears. Choose the following settings:
com.learnappmaking.Chat
.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:
AppDelegate.swift
import UIKit
, type: import Firebase
.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:
New File...
.Constants.swift
, add it to the Chat Target, and save the file in your project folder.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:
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…
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:
ChatViewController.swift
and locate the class definition at the top. This is the line that starts with class ...
.UIViewController
with JSQMessagesViewController
. An error should appear: Use of undeclared type.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:
It’s working! Let’s continue…
OK, now you have to configure JSQMVC for a bit. This is what you’re going to do:
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:
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!
outgoingBubble
and incomingBubble
, both of type JSQMessagesBubbleImage
.42
, but a computed value: the result of a function.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:
messages[indexPath.item].senderId == senderId
is true
, return outgoingBubble
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 neededcollectionView(_:collectionViewLayout:heightForMessageBubbleTopLabelAt:)
is called when the height of the top label is neededInside 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.
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?
/chats
node, using childByAutoId()
message
that contains all the information about the to-be-sent message: sender ID, display name, and chat textfinishSendingMessage()
, a function that tells JSQMVC you’re doneThe 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?
Command-R
Database -> Data
But… you aren’t seeing any chat bubbles yet. Let’s change that!
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:
JSQMessage
object is created, and added to the end of the messages
arrayfinishReceivingMessage()
is called, which prompts JSQMVC to refresh the UI and show the new messageLet’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?
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?
UserDefaults
, or with a random item from the array names
.UserDefaults
.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 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:
UserDefaults
jsq_id
and jsq_name
exist in the user defaults.
id
and name
to senderId
and senderDisplayName
senderId
and assign an empty string to senderDisplayName
senderId
in the user defaults, for key jsq_id
and save the user defaults (with synchronize()
)"Chat: [display name]"
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:
Neat!
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!
Want to learn more? Check out these articles and tutorials:
Check out the chat app, right here:
Enjoying this guide? Share it on social media!