🎉 New! Web Push Notifications for Chatkit. Learn more in our latest blog post.
Hide
Products
chatkit_full-logo

Extensible API for in-app chat

channels_full-logo

Build scalable realtime features

beams_full-logo

Programmatic push notifications

Developers

Docs

Read the docs to learn how to use our products

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Sign in
Sign up

Realtime notifications for chat messages: Part 2 - Building the iOS app

  • Neo Ighodaro
August 5th, 2019
You will need to have Xcode 10+ installed on your machine.

Introduction

In this part, we will see how to enable notifications for missed chat messages in Chatkit for iOS apps. What we will build will enable offline members of a group chat get notifications about messages.

In the previous part, we built an Android application that implemented the push notifications for Chatkit feature.

Prerequisites

Before going ahead, it is expected that you have the following:

  • Completed the first part of the series because we built the backend and set up Chatkit there.
  • Xcode v10+ installed on your machine. You can download here.
  • Knowledge of Swift and building basic iOS apps.
  • An iOS device if you want to test iOS push notifications.

Creating our iOS project

Open Xcode and create a new project. You should see a wizard like this:

Select the Single View App option and click Next. After that, you will be presented with another screen where you have have to enter the product name and organization identifier:

In our case, the app name is ChatkitNotifications. Select Next to choose where your project will be saved and then Xcode will set up the project for you.

The next thing we will do is to add our dependencies for the app. Create a Podfile in the project directory by running this command in the terminal:

    $ pod init

Replace what you have in the file with this:

    # File: Podfile
    platform :ios, '12.1'

    target 'ChatkitNotifications' do
      use_frameworks!

      pod 'PusherChatkit'
    end

After that, run this command to install the dependencies:

    $ pod install

After your dependencies finish installing, close your ChatkitNotifications.xcodeproj project and open the ChatkitNotifications.xcworkspace project in Xcode.

Next, open the Info.plist file as source code and inside the dict add this snippet:

    <!-- File: ChatkitNotifications/Info.plist -->
    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
        <!-- ... -->
    </dict>

This is used to put an exception to the security policy which explicitly requires a secure connections (this means you should never do this on a production build). Now, we will start designing the login page. Rename the default ViewController.swift file to LoginViewController.swift. Rename the declared class name inside the file too.

To quickly design the storyboard, you can copy the storyboard’s XML and replace your own with it.

Go to the Main.storyboard file, select the view controller and change the custom class through the identity inspector to LoginViewController.

After that, drag a text field element and drop at the center the view. First, add constraints to center the textfield vertically and horizontally as seen below:

Then, add more constraints like so:

Here, we added left and right constraints with a size of 20. Then we set 50 as the specific height for the textfield. After that, set the font size to 90 through the attributes inspector.

After that, drag a button and drop below the text field. Constraint the button as follows:

Here, we added a top constraint of 15, then left and right constraints of 20. Next, set the text to Login through the attributes inspector. Increase the font size to 19 too.

Now, let us add functionalities to the LoginViewController file. Replace your LoginViewController.swift file with this snippet:

    // File: ChatkitNotifications/LoginViewController.swift
    import UIKit
    import PusherChatkit

    class LoginViewController: UIViewController {

        var chatManager: ChatManager!
        var currentUser: PCCurrentUser?
        var chatManagerDelegate: PCChatManagerDelegate?

        @IBOutlet weak var textField: UITextField!

        @IBAction func login(_ sender: Any) {
            setupChatManager()
        }

        func setupChatManager() {
            self.chatManager = ChatManager(
                instanceLocator: "CHATKIT_INSTANCE_LOCATOR",
                tokenProvider: PCTokenProvider(url: "http://localhost:3000/token"),
                userID: self.textField.text!
            )

            self.chatManagerDelegate = MyChatManagerDelegate()

            self.chatManager.connect(
                delegate: self.chatManagerDelegate!
            ) { [unowned self] currentUser, error in
                guard error == nil else {
                    print("Error connecting: \(error!.localizedDescription)")
                    return
                }

                guard let currentUser = currentUser else {
                    print("CurrentUser object is nil")
                    return
                }

                currentUser.enablePushNotifications()
                self.currentUser = currentUser

                DispatchQueue.main.async() {
                    let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
                    let newViewController = storyBoard.instantiateViewController(withIdentifier:"chatViewController")
                        as! ChatViewController

                    newViewController.currentUser = currentUser

                    let navigationController = UINavigationController(rootViewController: newViewController)
                    self.present(navigationController, animated: true, completion: nil)
                }
            }
        }
    }

    class MyChatManagerDelegate: PCChatManagerDelegate {}

Replace the localhost URL with the ngrok URL from part one.

In this snippet, first, we declared the variables we will be using. Then we linked the textField outlet to the textfield on login view controller in the Main.storyboard file. We also have an action attached to our login button. When the button is selected, we trigger the setupChatManager method.

In the setupChatManager function, we initialize our chatManager instance with our token endpoint, the username, and our Chatkit app instance locator. When the connection is successful, we enable push notifications and open the ChatViewController.

Replace the CHATKIT_INSTANCE_LOCATOR with the real value on your dashboard.

Create a ChatViewController class and paste this snippet in the class:

    // File: ChatkitNotifications/ChatViewController.swift
    import UIKit
    import PusherChatkit

    class ChatViewController : UITableViewController {
    }

    extension ChatViewController: PCRoomDelegate {
        func onMultipartMessage(_ message: PCMultipartMessage) {
            self.messagesList.append(message)
            DispatchQueue.main.async {
                self.tableView.reloadData()
            }
        }
    }

In this snippet, apart from the declaration of the class, we have an extension class that we will use to receive new messages.

Open your Main.storyboard, drag a table view controller and then embed it into a navigation controller scene.

Let us design how each row of the table view will look like. We want each row to show us the sender’s name and the message itself. Now, drag two labels and drop on the table view cell. For the first label, add constraints like so:

Then, the second label should be placed and constrained like so:

For the second label, set the color to dark gray so that we can easily differentiate between the sender’s name and the message itself.

In the attributes inspector, set identifier for the table view cell to messageCell. In the identity inspector, set the custom class to ChatViewController and the storyboard ID to chatViewController.

Next, drag a bar button item and drop on the right-hand side of the navigation bar. In the attributes inspector, set the system item to Add. We will now stitch things up in the ChatViewController screen. Inside the class snippet you defined earlier, paste this:

    // File: ChatkitNotifications/ChatViewController.swift
    var messagesList = [PCMultipartMessage]() {
        didSet {
            self.tableView.reloadData()
        }
    }

    var currentUser: PCCurrentUser?

    @IBAction func sendNewMessage(_ sender: Any) {
        let alertController = UIAlertController(title: "New message", message: "Enter a new message", preferredStyle: .alert)

        let confirmAction = UIAlertAction(title: "Send", style: .default) { (_) in
            let newMessage = alertController.textFields?[0].text
            self.currentUser!.sendSimpleMessage(roomID: (self.currentUser!.rooms[0].id), text: newMessage!) { message, error in
                guard error == nil else {
                    print("Error sending message")
                    return
                }
                print("Sent message successfully")
            }
        }

        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (_) in }

        alertController.addTextField { (textField) in
            textField.placeholder = "Enter message"
        }

        alertController.addAction(confirmAction)
        alertController.addAction(cancelAction)

        self.present(alertController, animated: true, completion: nil)
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return messagesList.count
    }

    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 70
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let currentItem = messagesList[indexPath.row]
        let messageCell = tableView.dequeueReusableCell(withIdentifier: "messageCell") as! TableCell
        messageCell.setValues(message: currentItem)

        return messageCell
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        subscribeToRoom()
    }

    func subscribeToRoom() {
        currentUser!.subscribeToRoomMultipart(
            room: currentUser!.rooms[0],
            roomDelegate: self
        ) { error in
            guard error == nil else {
                print("Error subscribing to room: \(error!.localizedDescription)")
                return
            }
            print("Subscribed to room!")
        }
    }

In this snippet, we have our variables - messagesList that holds the messages for the room, currentUser that holds data on the logged in user. The value of the currentUser is passed from the previous view controller.

Then, we have an action attached to the bar button item. The action presents a dialog that helps to send new messages. Thereafter, we have overridden tableView functions that help us to set the height of each row, the size of the table view and binds data to each row.

In the viewDidLoad method, we call the subscribeToRoom method which helps us to subscribe to the first room on the user’s list.

Remember, in the table view overridden methods where we bind data to each row, we cast a row to the TableCell class. Now let us create the class.

After creating the class, replace what you have there with this:

    // File: ChatkitNotifications/TableCell.swift
    import UIKit
    import PusherChatkit

    class TableCell : UITableViewCell {
        @IBOutlet weak var username: UILabel!
        @IBOutlet weak var messagelabel: UILabel!

        func setValues(message:PCMultipartMessage) {
            for part in message.parts {
                switch part.payload {
                case .inline(let payload):
                    username.text = message.sender.displayName
                    messagelabel.text = payload.content

                case .url(let payload):
                    print("Received message with url: \(payload.url)")
                case .attachment(let payload):
                    payload.url() { downloadUrl, error in }
                }
            }
        }
    }

Here, we linked the labels in our table view cell to the class. You have to set the custom class of that table view cell to TableCell for that to be possible.

In the class, we have the setValues method that gets the sender of the message and the content and sets it to the labels.

We still need some extra things to get the notifications working. Open your AppDelegate file and replace it with this:

    // ChatkitNotifications/AppDelegate.swift
    import UIKit
    import PusherChatkit

    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {

        var window: UIWindow?

        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
            // Override point for customization after application launch.
            ChatManager.registerForRemoteNotifications()
            return true
        }

        func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
            ChatManager.registerDeviceToken(deviceToken)
        }
    }

Here in this file, we register for remote notifications and we register the device token with Chatkit.

Finally, in the project inspector select Capabilities tab and enable push notifications.

It’s important to note here that Chatkit will only send notifications to users that not currently online.

Uploading your keys to your Pusher dashboard

Navigate to the keys section in your Apple Developer Center, where you'll create an APNs Key. Enter the key name and make sure that APNs checkbox is checked. Press the Continue button. In order to generate the key, you'll either need to be an Account Holder or Admin, App Manager with Certificates, Identifiers & Profiles access enabled. You can learn more about the roles, here.

Press the Download button to download the key. Finalize the process and press the Done button. Now go to the Chatkit instance in the dashboard, and use this APNs Key to configure your iOS integration.

)

If you need more help setting up, check out the Chatkit documentation for iOS here.

Conclusion

In this tutorial, we have seen how we can enable notifications for chat apps for iOS apps using Pusher Chatkit. The code to the application is available on GitHub.

Clone the project repository
  • Android
  • Chat
  • iOS
  • Kotlin
  • Node.js
  • Swift
  • Java
  • Chatkit

Products

  • Channels
  • Chatkit
  • Beams

© 2019 Pusher Ltd. All rights reserved.

Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 160 Old Street, London, EC1V 9BW.