In Part One of this tutorial, we set-up an Xcode project and integrated Firebase using CocoaPods. Now it’s time to build the core chat functionality of our app! Open up Chat.xcworkspace, and we’ll let the code flow.
Create the Swift View Controllers
This app needs two view controllers. The first one will let the user pick a username. The second one will handle the chatting. Once a user has selected a username for the first time, we’ll simply direct him straight to the chat interface.
With that plan in mind, select File->New, and add a Swift file. Call it LoginViewController. Copy in the contents from ViewController.swift. Then change the class name in LoginViewController to the new name.
class LoginViewController: UIViewController {
Repeat those steps to create another Swift file called ChatViewController. Adjust the code in the same fashion for this class.
class ChatViewController: UIViewController {
We don’t need ViewController.swift anymore, so go ahead and delete it.
The Storyboard
Click on Main.storyboard. In the the view controller, add a label that says “Choose a Username” somewhere toward the top. Add a text field below it, where the user can type in his name. Below that, drag in a button, and change its text to “Confirm”. The placement doesn’t have to be perfect. Just keep it all in the top half of the view controller, so we have room to display the keyboard.
Now drag in a second view controller next to the first one. This will be for chat. Put a text view in the view controller and stretch it to fill out most of the top half of the view controller. Add a text field below it in the middle of the view controller. This gives us room to display the keyboard.
Control drag from the button in the first view controller to the second view controller. Create a modal segue. That takes care of the transition between the two view controllers!
Using the storyboard outline, select the first view controller. In the right panel, go to the Identity Inspector. In the Class dropdown menu, select LoginViewController. Also write LoginViewController for the Storyboard ID. Go through the same process to select ChatViewController for the other view controller, and set its Storyboard ID to ChatViewController. Your storyboard should look something like this.
Let’s connect these objects to the code. Use the Assistant Editor, and link your text field into LoginViewController.swift. Make a weak outlet named usernameField. Link the button into the code, and create an action called confirmButtonTapped with a UIButton as the sender. Your code should look like this.
@IBOutlet weak var usernameField: UITextField!
@IBAction func confirmButtonTapped(_ sender: UIButton) {
}
Link up the scroll view and text field in your other view controller to ChatViewController.swift. Make them weak outlets named chatTextView and chatTextField, respectively. Check if your code matches this.
@IBOutlet weak var chatTextView: UITextView!
@IBOutlet weak var chatField: UITextField!
We need some constraints to properly layout all of the objects, but in the interest of focusing on the chat and database work, I’ll let you position everything to your liking. In the meantime, go to your project settings and disable the landscape orientations.
There are a couple minor thing left to do in the Attributes Inspector. Set the Capitalization setting to Sentences for your text fields. For the text field in ChatViewController, change the Return Key to Send. Finally, delete the filler text out of the text view. That completes the storyboard!
Creating a Username
We need our users to have a consistent alias attached to their messages. Let’s have them choose one immediately when they enter the app. Add the following function at the bottom of LoginViewController.
func saveUsername() {
let userDefaults = UserDefaults.standard
userDefaults.set(usernameField.text, forKey: "username")
userDefaults.synchronize()
}
This saves the user’s chosen username locally. It’s also persistent across future sessions, so we’ll never have to ask the user for a username again. We will attach this name to all of the user’s future messages in chat. Let’s call this function when the confirm button is tapped. That function looks like this now.
@IBAction func confirmButtonTapped(_ sender: UIButton) {
saveUsername()
}
Finally, we’d like the keyboard to show immediately, so go ahead and display it with the following code in viewDidLoad.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
usernameField.becomeFirstResponder()
}
That’s it for LoginViewController. Remember, we only want the user to enter a username once. To handle this, go to AppDelegate.swift, and update your didFinishLaunching function as follows.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FIRApp.configure()
let userDefaults = UserDefaults.standard
if userDefaults.object(forKey:"username") != nil {
self.window = UIWindow(frame: UIScreen.main.bounds)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "ChatViewController")
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
}
return true
}
Now if we have a username stored already, we can just skip straight to the chat!
Database Read and Write Rules
By default, Firebase only grants read and write privileges to users who are authenticated through Firebase. We don’t want our users to worry about passwords or social network verification for this app. Instead, we’ll just change the database rules to allow anyone to read and write.
Go to your database in the Firebase console. Click Database, then click Rules. Change the permissions to the following.
{
“rules”:{
“.read”:true,
“.write”:true
}
}
It’s a free for all! In practice, it’s usually a good idea to authenticate your users, but this will do for tutorial purposes.
Posting Messages
We’re getting to the heart of the application! Head over to ChatViewController. For starters, we want to send messages from the keyboard instead of using a separate button. That means we need to make our class a UITextFieldDelegate. Adjust the class definition line as follows.
class ChatViewController: UIViewController, UITextFieldDelegate {
In viewDidLoad, add some additional text field code.
chatField.delegate = self
chatField.becomeFirstResponder()
The first line makes this class the recipient of a variety of text field notifications. The second line simply opens the keyboard, just like in LoginViewController. The delegate function we want to use is textFieldShouldReturn. Let’s add it at the bottom of the class.
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
// post chat
return true
}
This function will fire whenever the user hits the send button on the keyboard. We just need some Firebase code to get the message to the server.
Firebase Write Code
Start by importing Firebase and its database library into ChatViewController.
import Firebase
import FirebaseDatabase
In order to make any Firebase calls, you need a reference to the database. At the top of your class (right above your IBOutlets), create a database reference variable.
var ref: FIRDatabaseReference!
In viewDidLoad, we’ll define it. Add this just below your chatField code.
ref = FIRDatabase.database().reference()
That keeps track of the database. Let’s add some code to write to it. Edit textFieldShouldReturn to look like this. Then, we can review what it all does.
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
// post chat
let userDefaults = UserDefaults.standard
let username = userDefaults.value(forKey: "username") as! String
let message = username + ": " + chatField.text!
let key = ref.child("messages").childByAutoId().key
let childUpdates = ["/messages/\(key)": message]
ref.updateChildValues(childUpdates)
chatField.text = ""
return true
}
First, we create a message by combining the user’s username and the message he typed. That’s the easy part!
Second, we store it to the database. This takes some explanation. We use a child node in our database called messages. This allows us to keep all of our messages in one place. If we have other data to store for this app, we can keep it separated under a different child node.
We have to store all of our messages here in some sort of time stamped fashion, so users can retrieve them all in sequential order. Firebase has a set method that you often use for writing to the database. If you try to directly set your message to the messages node, however, you’ll overwrite everything that’s there! That’s why we autogenerate a key that gives us a sequential string for that node. Then we upload our message as a key-value pair. This prevents us from overwriting anything, and maintains the messages in chronological order.
Finally, we clear out the textfield, so the user can write something else.
Try writing and sending some messages. You won’t see them on your app yet, since we’re not reading anything from the database. You should see them in your Firebase console, though. Go to the Firebase console, click on your app, and select Database from the left menu. You’ll find the messages node full of all your texts!
Firebase Read Code
It’s pretty cool to see all this in the backend, but we want to view it in the app! That’s easy enough. Just add the following code to the end of viewDidLoad, and you’re good to go.
ref?.child("messages").observe(FIRDataEventType.value, with: { (snapshot) in
self.chatTextView.text = ""
let messageDictionary = snapshot.value as? [String : String] ?? [:]
let sortedKeys = Array(messageDictionary.keys).sorted(by: <)
for key in sortedKeys {
let message = messageDictionary[key]!
self.chatTextView.text = self.chatTextView.text + "\n" + message
}
let range = NSMakeRange(self.chatTextView.text.characters.count - 1, 0)
self.chatTextView.scrollRangeToVisible(range)
}, withCancel: nil)
Let me explain what we’re doing here. This function establishes us as an observer to changes in the messages node. Whenever we write a message or another user posts one, this function will receive a call. We spring into action by clearing out all the text in the text view. Then we grab the dictionary of messages. Dictionaries don’t typically have an order, but we generated all the keys to denote the sequence of the messages. Therefore, we can sort the keys, then retrieve the corresponding values (the messages), and display them one after another in the text view. Once they’re all written out, we programmatically scroll down to the bottom of the text view.
That’s All For Now
We’ve done it! If you only have one device, try opening a simulator and text back and forth with your actual device. You should see the new messages appear almost instantaneously. Pretty cool, right? This is all good and well if you’re chatting with someone who has the app open, but what if your chat partner has decided to close the app for a moment? Sounds like a good teaser for push notifications! We’ll cover those next time. Until then, add some constraints to tidy up your interface, distribute the app to your friends, and start conducting all of your correspondence on your own app!