• How to Build an iOS Mobile Group Chat App with Swift 5
  • Samba Diallo
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: LucaslEliane
  • Proofread by: Jiang Wu Ru, Endone

Build iOS mobile terminal group chat App with Swift 5

Whether it’s a standalone group chat app, an embedded customer service component, or a private one-on-one chat within a dating app, mobile chat is everywhere, of all features and sizes.

In this tutorial, we will show you how to use Swift 5 to build an iOS mobile chat application that allows any number of users to chat in real time. We’ll also show you how to store message history so that when users leave and come back, their messages are still in the application.

To implement the above application, we used some key PubNub features: Publish/subscribe (live messaging) and store & Playback (message storage).

  • Publishing is how each client sends its message to the world, or at least to the channel it wants to publish. The simplest use of the Pub/Sub mode is to send every message you send to anyone who subscribes to a channel. Publishing requires an instance of a PubNub connection (which I’ll cover in more detail later), the message message to send (of type String, NSNumber, Array, and Dictionary), and the channel to which we want to send the message. Learn more about Swift releases.
  • Subscriptions are another part of PubNub instant messaging. To subscribe, we need an instance of a PubNub connection and a channel to subscribe to. After a successful subscription, we receive messages, but if we don’t process them when they arrive, we still don’t see them. Learn more about Swift subscriptions.
  • Updates for event handling or listening are important in the life cycle of PubNub. While Pub/Sub is very compelling, the key to using PubNub is the event handler, which connects the data flow network to our console and applications. One of them listens exclusively for messages, while the other looks for anything else, including subscription changes and errors.
  • Storage and playback is another key to this great feature set. If storage and message retrieval are already imported into your project, storage and play are also good additions to your application. This feature allows you to retrieve historical messages. The scope of an application message spans the entire life cycle of the application. We will set up the PubNub account and get the API key, setting the lifetime of the storage in the PubNub administrative console. Learn more about storage and playback in Swift.

After watching this tutorial, you will have implemented an application that provides chat room services, and this application can be a good base or complement to any other application.

The full Swift 5 iOS chat app can be found here.

build

PubNub

If you don’t already have a PubNub account, you can sign up for one here. Once logged in, create a new application. Click it and create a new key set or click the existing demo. You should now see publish and subscribe keys through which we can use the PubNub API.

Under Keys, we can enable different options! Let’s enable storage and playback near the lower left corner. We can now decide how long you wish to keep messages. I selected a retention period of one day and saved the changes. Under the reserve setting, you can also set to enable deletion from PubNub history.

Xcode application build

Open Xcode and create a new project, select the single-view application, give it a name, and close the project. Use the terminal to navigate to the project folder and run gem Install Cocoapods or gem update Cocoapods to update your existing installation.

Create a Podfile for your application by typing Touch Podfile in terminal, and then open the file with Open Podfile.

Write the following code to the file, making sure to replace “application-target-name” with the name of the project.

The source 'HTTPS://github.com/CocoaPods/Specs.git'If compilation problems occur, you can choose to uncomment the following and complete # project '<path to project relative to thisPodfile>/<name of project without extension# > 'workspaceMyPubNubProject'use_frameworks! # Replace the quotes in the next line with your project nametargetapplication-target-name'do# The following configuration only applies to # with a minimum compile goal of #iOS8 projectsplatform :ios'8.0' # (or'9.0'or"10.0")podPubNub", "~ 4" >Copy the code

Then run the pod install command on the terminal. This command will help you install PubNub in your project. After the installation is complete, double-click the.xcworkspace file to open the project project.

Designing applications

Before we get into all the logic, let’s design and build the view of the application. Let’s start with the login view.

Rename viewController.swift to Connectv.swift by highlighting the name in the class declaration and going to Editor -> Refactor -> Rename.

When users open the application, in addition to the connect button, we want them to have a field to enter the username and channel they want to connect to. Add these to your first view. Also, I chose Topically for the title of our app, but you can also choose a cooler title.

I then control drag my storyboard project into my ConnectVC file to set up an outlet for my username and my channel’s TextFields. Do the same for the button, but instead of using an outlet, create a UIButton action that performs the action when it’s pressed.

Next, let’s create the channel chat view.

Create a new Cocoa Touch class and name it ChannelVC. Create a new view controller in the storyboard and set the class to ChannelVC. When selecting this view, go to the top of the screen and click Editor -> Embed In -> Navigation Controller. The other view should now be in your storyboard. This is the navigation controller, which allows the user to switch between entering the view.

Add a UIBarButtonItem to the left of the ChannelVC navigation bar, which is our “Leave” button. Hold down the Control key and drag it to channelvc. swift and create an action of type leaveChannel, UIBarButtonItem. Drag the UITableView into the ChannelVC view. Make it take up most of the screen, but you need the outflow space to put another TextField and a button with the text Send. Create them.

Create outlets for the table and TextField in channelvc. swift, and add another action for the send button.

Our next step does not involve our ChannelVC, but creating custom cells within our table. Once we have the overall layout set by ChannelVC, we have to customize the cells in the tableView. Create a new Cocoa Touch class and call it MessageCell, and drag the UITableViewCell into the table view. Set the Cell class as a new class and change the identifier to MessageCell.

Drag anything to complete the design you want and any details you need. We put the user name and message label into the cell, and when we’re done, hold down the Ctrl key and drag to create an outlet for the MessageCell class. Be sure to set style constraints so that the table does not compress your content.

For more information about making your application fit all screen sizes, seeApple’s documentation on automatic layoutOr check out the many online guides.

Now we have a lot of nice views, but we can’t switch freely between them. Click the bar above ConnectVC with its name, and then click the yellow circle. Hold down the Control key and drag it to the navigation controller and select the Show option. Select the navigation controller and click the properties TAB on the right panel, which displays “Storyboard Segue” at the top. Name the identifier “connectSegue”. When you click the Connect button on ConnectVC, you perform this Segue.

The next and final task we need is to navigate us from ChannelVC to ConnectVC. Select ChannelVC in the same way as ConnectVC and drag it to ConnectVC. This time select “Present Modally” and name it “leaveChannelSegue” in the property inspector.

Code: ConnectVC

Now that we’re done with the storyboard, let’s start coding. We’ll start with ConnectVC, which provides username and channel for our ChannelVC, and we’ll use all of our PubNub knowledge. First, we perform the segue in our connect operation.

@IBAction func connectToChannel(_ sender: UIButton) {
    self.performSegue(withIdentifier: "connectSegue", sender: self)}Copy the code

This takes advantage of the connectSegue we made in the last section, which navigates us to ChannelVC’s navigation controller. The only thing we need to do in this view controller is prepare for the segue up here. By overriding this feature, we can send information between views.

Note: In this tutorial, if A user does not provide A user name, I will automatically assign the user name “A Naughty Moose”. If they don’t have a channel, I send them to channel “General”.

To access the view we want to access, we need to get an instance of the navigation controller, and then get our ChannelVC view from there. We check if the text field is empty, replace the value if necessary, and then set the two variables in ChannelVC that we haven’t created yet, using our username and channel.

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

    // Access the navigation controller and ChannelVC view
    if let navigationController = segue.destination as? UINavigationController.let channelVC = navigationController.viewControllers.first as? ChannelVC{
        var username = ""
        var channel = ""

        // Replace the empty string below with a default value you want
        if(usernameTextField.text == "" ){
            username = "A Naughty Moose"
        }
        else{
            username = usernameTextField.text ?? "A Naughty Moose"
        }
        if(channelTextField.text == "") {print("nothing in channel")
            channel = "General"
        }
        else{
            channel = channelTextField.text ?? "General"
        }

        // Set the ChannelVC variable
        channelVC.username = username
        channelVC.channelName = channel
    }
}
Copy the code

Code: ChannelVC

In our ChannelVC, we should have two outlets, an action, and our viewDidLoad function. Most importantly, under the class definition, we’ll start defining some of the variables and resources we need for the rest of the class.

First, let our class listen for PubNub events and make them work with our table. Introduce PubNub at the top of the file, after the UIViewController in our class definition says PNObjectEventListener, UITableViewDataSource, and UITableViewDelegate. Our class should now display the error, click on the error and add the suggested classes for easy introduction.

  • Under our class definition, let’s define a structure that makes it easier to process our messages. My structure has three strings: message, username, and UUID. Later when we publish the message, you can send different messages and use these to update the structure.
  • After that, you create a Message array and initialize it to empty, because all class variables need to have some initial value.
  • Create a token of type NSNumber for the earliest message we receive and set the initial value of the token to -1.
  • Another variable to track whether we have started loading more messages.
  • Now, the most important variable for this view controller to publish and subscribe is the object on which we will call the PubNub function in this view controller.
  • We then get the username and channel that the user entered in the last step and initialize it with temporary values.
  • After that should be our message text field, our tableView, and our send action.
class ChannelVC: UIViewController.PNObjectEventListener.UITableViewDataSource.UITableViewDelegate {

    // Our message structure can make message processing easier
    struct Message {
        var message: String
        var username: String
        var uuid: String
    }
    var messages: [Message] = []

    // Keep track of the first message we loaded
    var earliestMessageTime: NSNumber = -1

    // to keep track of whether we have loaded more messages
    var loadingMore = false

    // We use PubNub objects to publish, subscribe and get content from our channels
    var client: PubNub!

    / / provisional value
    var channelName = "Channel Name"
    var username = "Username"

    //-- should already exist in your file
    // Message entry
    @IBOutlet weak var messageTextField: UITextField!

    // We populate the View with information from the Messages array
    @IBOutlet weak var tableView: UITableView!

    / /...

}
Copy the code

Now that we’ve set up some global variables that we can use throughout our code, let’s set up the viewDidLoad function. After calling the inherited viewDidLoad, change the title at the top of the navigation controller to the channel name and set the Table View’s delegate data source to self.

self.navigationController? .navigationBar.topItem? .title = channelName tableView.delegate =self
tableView.dataSource = self
Copy the code

Next, we configure and initialize our PubNub object. Here you can insert publish and subscribe keys from your PubNub account. We set stripMobilePayload to false because it is deprecated and provides a unique UUID for this connection, which makes it easier to develop more functionality in the future. It then initializes it, sets it up as a listener, and subscribes to the channel selected by the user. We then call the method loadLastMessages that we will create in the next step.

// Set our PubNub object!
let configuration = PNConfiguration(publishKey: "INSERT PUBLISH KEY", subscribeKey: "INSERT SUBSCRIBE KEY")
// Delete deprecated warnings
configuration.stripMobilePayload = false
// Set flags for each connection for future development
configuration.uuid = UUID().uuidString
client = PubNub.clientWithConfiguration(configuration)
client.addListener(self)
client.subscribeToChannels([channelName],withPresence: true)

// We load the last message to fill the TableView
loadLastMessages()
Copy the code

There should now be an error saying that the function we call at the end of viewDidLoad is undefined, so let’s define it! This feature is used to load the initial message when connecting to the channel. It takes advantage of another function that we’re going to create called addHistory.

Let’s call the next function, start and end with nil, and then set the number of messages you want to receive up to 100. Our last action inside the function is to scroll our table View down to the new message at the bottom of the table.

// This function is called when the view is initialized to load to fill the TableView
func loadLastMessages(a)
{
    addHistory(start: nil, end: nil, limit: 10)
    // Scroll the TableView to the bottom of the latest message
    if(!self.messages.isEmpty){
        let indexPath = IndexPath(row: self.messages.count-1, section: 0)
        self.tableView.scrollToRow(at: indexPath, at: .bottom, animated: true)}}Copy the code

Store and play back historical messages

Now, we can review the history of our channel through this function. Create it with a few key parameters, only the limit parameter is required, and then call the function to allow us to view the channel history.

We use historyForChannel, a function with many overloaded versions. We can use simple messages that return the last 100 messages or receive messages with start and end times. Both methods are handled by PNHistoryResultBlock, which allows us to access query results and errors.

First, let’s check if the result is non-empty, and if so, we can start accessing the message! Once we know that our messages contain at least some content, we can start accessing them. We need to update our earlistMessage start time with the earliest message we receive in the results. Next we convert our returned object to an object we can use, an array of String keys.

We can create a Message object from this new object, add them to a temporary array, and then insert it at the beginning of our global Message array instead of trying to access the objects directly each time. Be sure to reload the form and check for errors!

// Gets and puts the channel's history messages into the Messages array
func addHistory(start:NSNumber? ,end:NSNumber? ,limit:UInt){
    // The PubNub function, which returns the object of the X message and then sends the first and last messages
    // limit is the number of messages received. The maximum value is 100. The default value is 100
    client.historyForChannel(channelName, start: start, end: end, limit:limit){ (result, status) in
        if(result ! =nil && status == nil) {// When we want to load more, we save the time of the earliest message sent in order to retrieve the previous message.
            self.earliestMessageTime = result! .data.start// Convert the [Any] package we obtained into a String and Any dictionary
            letmessageDict = result! .data.messagesas! [[String:String]]

            // Create new messages from it and place them at the end of the message array
            var newMessages :[Message] = []
            for m in messageDict{
                let message = Message(message: m["message"]! , username: m["username"]! , uuid: m["uuid"]!) newMessages.append(message) }self.messages.insert(contentsOf: newMessages, at: 0)
            Reload the TableView with the new message and scroll down to the bottom of the latest message
            self.tableView.reloadData()

            // Make sure we can't try to reload any more data until the load is complete
            self.loadingMore = false
        }
        else if(status ! =nil) {print(status! .category) }else{
            print("everything is nil whaaat")}}}Copy the code

Next, let’s fill in the functions needed for tableView. The first, numberOfRowsInSection, is a simple one-line function that returns the number of messages in an array. In the second function, we first need to get an instance of the message cell and set the text of the cell label to the user name of the message and message array index. Then, just return the cell!

// The tableView function is required
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // Change it later
    return messages.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "MessageCell") as! MessageCell

    cell.messageLabel.text = messages[indexPath.row].message
    cell.usernameLabel.text = messages[indexPath.row].username


    return cell
}
Copy the code

One of the most important parts of using and debugging PubNub in Swift is creating listeners for events and messages. In this application, we use the function didRecieveMessage, which allows us to access messages that come into our channel. The logic inside this function is a stripped-down version of our loadLastMessages function.

Check that incoming messages match the channel we subscribe to, in case we subscribe to content from another channel. Take our message and convert it to an array of key values of type String. Use the Dictionary to create a message and bind it to the end of the message array.

Reload the data again, then scroll down to the new message. You can change this depending on the implementation you want. I print messages in the console for debugging.

func client(_ client: PubNub, didReceiveMessage message: PNMessageResult) {
    Each time we receive a new message, we add it to the end of our message array
    // Reload the table so that messages are displayed at the bottom

    if(channelName == message.data.channel)
    {
        let m = message.data.message as! [String:String]
        self.messages.append(Message(message: m["message"]! , username: m["username"]! , uuid: m["uuid"]!) ) tableView.reloadData()let indexPath = IndexPath(row: messages.count-1, section: 0)
        tableView.scrollToRow(at: indexPath, at: .bottom, animated: false)}print("Received message in Channel:",message.data.message!)
}
Copy the code

Now we can load some history messages when we first open the channel, and they will show up at the bottom when new messages are sent.

What if there are more messages than we originally loaded? So in this new function, scrollViewDidScroll, when we’re pulling down from the top, we’re extracting some other messages from historyForChannel. This function can also be modified so that it can be preloaded when the user does not reach the top of the page to help achieve an infinite scrolling effect.

We have a global variable called loadingMore, which we start by checking if more messages have been loaded, and then check if the user scrolls past a certain threshold to start loadingMore messages. Thankfully, using PubNub is very fast, so it loads almost instantly. Written messages are written widely, and if there are any more history messages, we set loadingMore to true and start calling our addHistory function, to start with written messagetime and end with nil, allowing you to set limits according to your needs, Although the maximum number of messages returned is 100.

// This method allows users to query for more messages by dragging down from the top
func scrollViewDidScroll(_ scrollView: UIScrollView){
    //If we are not loading more messages already
    if(! loadingMore){// Drag from the top of the message down beyond -40
        if(scrollView.contentOffset.y < -40 ) {
            loadingMore = true
            addHistory(start: earliestMessageTime, end: nil, limit: 10)}}}Copy the code

We now need to publish the message when we click the Send button. To do this, let’s create a function to send messages in the messageTextField. First, we check if the messageTextField is empty, if so, process it, then create a dictionary to contain the message information you want to send, and then use the simple publish function on the PubNub object.

This function accepts variables and objects of various types and sends them as messages and channel names. You can also include a handler in the callback to perform certain actions based on the status code. After that, call the function we just created in the sendMessage operation.

func publishMessage(a) {
    if(messageTextField.text ! =""|| messageTextField.text ! =nil) {let messageString: String = messageTextField.text!
        let messageObject : [String:Any] =
            [
                "message" : messageString,
                "username" : username,
                "uuid": client.uuid()
        ]

        client.publish(messageObject, toChannel: channelName) { (status) in
            print(status.data.information)
        }
        messageTextField.text = ""}}// When the send button is clicked, a message will be sent
@IBAction func sendMessage(_ sender: UIButton) {
    publishMessage()
}
Copy the code

In order for our application to work fully, we need to be able to leave the channel and return ConnectVC. We already have this feature, we just need to fill it out. Unsubscribe all the channels the customer subscribed to, and then execute the “leaveChannelSegue” we originally created.

client.unsubscribeFromAll()
self.performSegue(withIdentifier: "leaveChannelSegue", sender: self)
Copy the code

Run the App

Let’s run the application!

We now have a basic chat function. Users can send and receive messages in real time, and history messages can be stored for a period of time.

The full Github repository for this project is here

PubNub offers a million free messages a month. There are PubNub Swift SDK documentation, as well as other 75+ PubNub client SDKs.

If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.