Build a Swift chat app
A basic understanding of Swift and Node.js are needed to follow this tutorial.
We thought we’d create a walkthrough of how to easily build an iOS chat app with Pusher. Together we will build a group chat application, leading you through how to use Pusher to send and show realtime messages in your UI.
The app we’re building
The app we’re building is a simple chat application that uses Pusher to send and receive messages.
It has two screens - the “Login” screen where we enter our Twitter username, and the “Chat” screen where we do the messaging.
Setting up our project with XCode
If you haven’t yet, create a new application on XCode. By default the wizard offers you to create a Single View Application for iOS, and that’s perfectly fine. Once you’ve done that, you’ll need to prepare the dependencies for the app. The dependencies you need are Pusher Swift for interaction with Pusher, and AlamofireImage for performing network requests, and loading images over the network.
The easiest way install dependencies is by using CocoaPods. If you don’t have CocoaPods installed you can install them via RubyGems.
gem install cocoapods
Then configure CocoaPods in our application. First initialise the project by running this command in the top-level directory of your XCode project:
pod init
This will create a file called Podfile
. Open it, and make sure to add the following lines specifying your app’s dependencies:
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
target 'Pusher Chat Sample iOS' do
# Comment the next line if you're not using Swift and don't want to use dynamic frameworks
use_frameworks!
# Pods for Pusher Chat Sample iOS
pod 'PusherSwift'
pod 'AlamofireImage' //need that for networking
end
And then run pod install
to download and install both dependencies.
pod install
CocoaPods will ask you to close XCode if it’s currently running, and open the newly generated .xcworkspace
file. Do this now, and XCode will open with your project configured.
Creating the Login View
For our login feature we’ll just create a simple page with a field to enter a twitter handle and a login button.
First rename our scene to “Login Scene”, and then drag the two elements onto it.
Also rename the ViewController
to LoginViewController
.
Control-drag
the each element into the LoginViewController
class to create the IBOutlet (for the TextView) and the IBAction for the button.
Name the IBOutlet twitterHandle
and IBAction loginButtonClicked
.
In your LoginViewController.swift
add the following logic to the loginButton
function:
@IBAction func loginButtonClicked(_ sender: Any) {
if(twitterHandle.hasText){
let messagesViewController = self.storyboard?.instantiateViewController(withIdentifier: "chatViewController") as! ChatViewController
messagesViewController.twitterHandle = twitterHandle.text!
self.present(messagesViewController, animated:true)
}
else{
print("No text in textfield")
}
}
This will grab the current text in the twitterHandle
field and set it to the ChatViewController
, and transition to the Chat screen.
Chat View
But the ChatViewController
doesn’t exist yet! Create a new ViewController in the Storyboard and the corresponding ChatViewController.swift
class.
Add to it a TableView, a Text Field, and a Button as in the example.
Listening to messages
We will listen to new messages in realtime by subscribing to the chatroom
channel and listening to events tagged new_message
.
Pusher channels can support unlimited number of message types, but in our case we are only interested the single one.
In viewDidLoad
create your Pusher instance - and copy your setup details from the Pusher Dashboard. It shoud look like this:
pusher = Pusher(
key: "abcdefghijklmnopqrstuvwxyz"
)
Then subscribe to the chatroom
channel, and bind to the new_message
events, printing their messages to the console. Lastly, connect to Pusher.
let channel = pusher!.subscribe("chatroom")
let _ = channel.bind(eventName: "new_message", callback: { (data: Any?) -> Void in
if let data = data as? [String: AnyObject] {
let text = data["text"] as! String
let author = data["name"] as! String
print(author + ": " + text)
}
})
pusher!.connect()
Now that we’ve subscribed and listening to the events, we can send some events to test it out. The easiest way to do this is by using Pusher’s Debug Console - in your app’s Dashboard. Have the application running - Simulator is fine.
Click Show Event Creator button, and change the name of Channel to chatroom
, and change the Event to new_message
- what we’re listening to in the app.
Now change the Data field to something like:
{
"name": "John",
"text": "Hello, World!"
}
And click Send event. In the XCode’s console, you should see the message printed out:
John: Hello, World!
Presenting messages in a table
Now, let’s show the messages as they arrive in the UITableView.
We will create a Prototype cell in the UITableView in the Storyboard, and specify a class for it.
Create a MessageCell.swift
class and make it extend UITableViewCell
. This will represent a single chat message as a row in our table. Drag the outlets for authorAvatar
, authorName
, and messageText
into the class. This
import Foundation
import UIKit
class MessageCell: UITableViewCell {
@IBOutlet var authorAvatar: UIImageView!
@IBOutlet var authorName: UILabel!
@IBOutlet var messageText: UILabel!
}
Now create a Message.swift
which will hold a struct representing a single Message object. It just needs to hold two strings, for the author and message.
import Foundation
struct Message {
let author: String
let message: String
init(author: String, message: String) {
self.author = author
self.message = message
}
}
Back in the ChatViewController.swift
, make the class implement the protocols UITableViewDataSource
and UITableViewDelegate
alongside UIViewController
:
class ChatViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
To make it compile, you’ll need to implement the following methods - first one to let the tableView know how many items it holds:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array.count
}
And the second one that will create a MessageCell
object:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MessageCell", for: indexPath) as! MessageCell
return cell
}
Then we need to add some logic that will actually present the data in a cell. Add these lines to the second method:
let message = array.object(at: indexPath.row) as! Message
cell.authorName.text = message.author
cell.messageText.text = message.message
let imageUrl = URL(string: "https://twitter.com/" + message.author + "/profile_image")
cell.authorAvatar.af_setImage(withURL: imageUrl!)
First we set up the text in the author and message labels, and lastly we use the AlamofireImage library to load the image from Twitter avatar into the authorImage
field.
Sending messages from the app
Building the serverside component in NodeJS
So far, we’ve created a client that receives items. But what about sending them? We’ll do that next.
First, we’ll need a server-side component that receives messages and sends them back to Pusher.
We prepared a simple NodeJS application that will serve that purpose. You can find it here.
First clone the repository and CD into its directory. Then run npm install
to setup dependencies.
Then open app.js
and change the Pusher initialisation fields there to include your App ID, key and secret. You can copy these from your Pusher Dashboard - the Getting Started tab will have everything you need.
Once you’ve done that you can launch the app by running node app.js
.
If your iOS app is running on your simulator, and your Node app is running the server, you should be able send a test message via the cURL
command:
$ curl -X "POST" "http://localhost:3000/messages" -H "Content-Type: application/json; charset=utf-8" -d $'{"name": "Pusher","text": "Hello, Node!"}'
If everything works as it should, you should see the new message appear in your app.
Building the app component
The last thing to do is to create the function that triggers and sends the message to us.
First make sure your Text Field and Button have their corresponding outlets in ChatViewController.swift
:
@IBOutlet var message: UITextField!
@IBAction func send(_ sender: Any) {
if(message.hasText){
postMessage(name: twitterHandle, message: message.text!)
}
}
Finally, we can implement the postMessage
function that calls our NodeJS endpoint to trigger a new message over Pusher:
func postMessage(name: String, message: String){
let params: Parameters = [
"name": name,
"text": message
]
Alamofire.request(ChatViewController.MESSAGES_ENDPOINT, method: .post, parameters: params).validate().responseJSON { response in
switch response.result {
case .success:
print("Validation successful")
case .failure(let error):
print(error)
}
}
}
Try it out!
If you are running the Node server locally XCode might not allow you to make the request. You can get around this by adding App Transport Security Settings
to your Info.plist
file and set Allow Artibrary Loads
to YES
.
Get Pushing
Hopefully you have found this a straightforward example of how to build an iOS chat app with Pusher. There are many ways you can extend this tutorial for an improved application:
- Use Pusher client events to send messages from one client to another. You could use our webhooks to notify your server when messages are sent, allowing you to persist them in your database.
- Use Pusher presence channels to create live user lists and show who’s online in realtime.
- Use our REST API to determine whether a user is connected to Pusher. If they are, go ahead and send them a message as normal. If not, send them a native push notification leading them back to your app.
Even with such a basic app, hopefully I have shown you how easy and few lines of code it is to drop in Pusher to any iPhone app. Feel more than free to let us know what you end up building with Pusher and iPhone!
16 February 2017
by Zan Markan