Send push notifications in a social network iOS app - Part 2: Build the app
Tags: pusher, ios, swift, realtime
Published: 2018-11-19 17:17:10

---
title: Send push notifications in a social network iOS app - Part 2: Build the app
published: true
description: 
tags: pusher, ios, swift, realtime
---

> To follow this tutorial you will need a Mac with Xcode installed, knowledge of Xcode and Swift, basic knowledge of PHP (including the Laravel framework), a Pusher account, and CocoaPods installed on your machine.

In the [previous part](https://dev.to/neo/send-push-notifications-in-a-social-network-ios-app---part-1-build-the-backend-ld5), we were able to set up our Pusher Beams application and also create our API backend with Laravel. We also added push notification support to the backend using the [pusher-beams](https://github.com/neoighodaro/pusher-beams) package.

In this part, we will continue where we left off. We will be creating the iOS application using Swift and then integrate push notifications to the application so we can receive notifications when they are sent.

![](//images.ctfassets.net/1es3ne0caaid/51GgQzYiqQEMAQWOK8AMgS/fd0d5bb1bbaa7f1f198fed340d25cd53/ios-push-notifications-social-network-demo.gif)

## Prerequisites

In order to follow along in this tutorial you need to have the following:

- Have completed [part one](https://pusher.com/tutorials/social-notifications-ios-part-1) of the article.

## Building our iOS application using Swift

### Creating our controllers

In Xcode, create a new class `LaunchViewController` and paste the contents of the file below into it:

```
    <span class="hljs-keyword">import</span> UIKit

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LaunchViewController</span>: <span class="hljs-title">UIViewController</span> </span>{
        <span class="hljs-meta">@IBOutlet</span> <span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> loginButton: <span class="hljs-type">UIButton</span>!
        <span class="hljs-meta">@IBOutlet</span> <span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> signupButton: <span class="hljs-type">UIButton</span>!

        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">viewDidLoad</span><span class="hljs-params">()</span></span> {
            <span class="hljs-keyword">super</span>.viewDidLoad()

            loginButton.isHidden = <span class="hljs-literal">true</span>
            signupButton.isHidden = <span class="hljs-literal">true</span>

            loginButton.addTarget(<span class="hljs-keyword">self</span>, action: #selector(loginButtonWasPressed), <span class="hljs-keyword">for</span>: .touchUpInside)
            signupButton.addTarget(<span class="hljs-keyword">self</span>, action: #selector(signupButtonWasPressed), <span class="hljs-keyword">for</span>: .touchUpInside)
        }

        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">viewDidAppear</span><span class="hljs-params">(<span class="hljs-number">_</span> animated: Bool)</span></span> {
            <span class="hljs-keyword">super</span>.viewDidAppear(animated)

            <span class="hljs-keyword">guard</span> <span class="hljs-type">AuthService</span>.shared.loggedIn() == <span class="hljs-literal">false</span> <span class="hljs-keyword">else</span> {
                <span class="hljs-type">SettingsService</span>.shared.loadFromApi()
                <span class="hljs-keyword">return</span> performSegue(withIdentifier: <span class="hljs-string">"Main"</span>, sender: <span class="hljs-keyword">self</span>)
            }

            loginButton.isHidden = <span class="hljs-literal">false</span>
            signupButton.isHidden = <span class="hljs-literal">false</span>
        }

        <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">loginButtonWasPressed</span><span class="hljs-params">()</span></span> {
            performSegue(withIdentifier: <span class="hljs-string">"Login"</span>, sender: <span class="hljs-keyword">self</span>)
        }

        <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">signupButtonWasPressed</span><span class="hljs-params">()</span></span> {
            performSegue(withIdentifier: <span class="hljs-string">"Signup"</span>, sender: <span class="hljs-keyword">self</span>)
        }   
    }
```

Set the controller as the custom class for the related storyboard scene.

Above we have two `@IBOutlet` buttons for login and signup. In the `viewDidLoad` method we hide the buttons and create a target callback for them when they are pressed. In the `viewDidAppear` method we check if the user is logged in and present the timeline if so. If the user is not logged in we unhide the authentication buttons.

We also have the `loginButtonWasPressed` and `signupButtonWasPressed` methods. These methods present the login and signup controllers.

Next, create a `SignupViewController` class and paste the following code into the file:

```
    <span class="hljs-keyword">import</span> UIKit
    <span class="hljs-keyword">import</span> NotificationBannerSwift

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SignupViewController</span>: <span class="hljs-title">UIViewController</span> </span>{
        <span class="hljs-meta">@IBOutlet</span> <span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> nameTextField: <span class="hljs-type">UITextField</span>!
        <span class="hljs-meta">@IBOutlet</span> <span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> emailTextField: <span class="hljs-type">UITextField</span>!
        <span class="hljs-meta">@IBOutlet</span> <span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> passwordTextfield: <span class="hljs-type">UITextField</span>!
        <span class="hljs-meta">@IBOutlet</span> <span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> signupButton: <span class="hljs-type">UIBarButtonItem</span>!

        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">viewDidLoad</span><span class="hljs-params">()</span></span> {
            <span class="hljs-keyword">super</span>.viewDidLoad()

            activateSignupButtonIfNecessary()

            nameTextField.addTarget(<span class="hljs-keyword">self</span>, action: #selector(textFieldChanged(<span class="hljs-number">_</span>:)), <span class="hljs-keyword">for</span>: .editingChanged)
            emailTextField.addTarget(<span class="hljs-keyword">self</span>, action: #selector(textFieldChanged(<span class="hljs-number">_</span>:)), <span class="hljs-keyword">for</span>: .editingChanged)
            passwordTextfield.addTarget(<span class="hljs-keyword">self</span>, action: #selector(textFieldChanged(<span class="hljs-number">_</span>:)), <span class="hljs-keyword">for</span>: .editingChanged)
        }

        <span class="hljs-meta">@IBAction</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">closeButtonWasPressed</span><span class="hljs-params">(<span class="hljs-number">_</span> sender: <span class="hljs-keyword">Any</span>? = <span class="hljs-literal">nil</span>)</span></span> {
            dismiss(animated: <span class="hljs-literal">true</span>, completion: <span class="hljs-literal">nil</span>)
        }

        <span class="hljs-meta">@IBAction</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">signupButtonWasPressed</span><span class="hljs-params">(<span class="hljs-number">_</span> sender: <span class="hljs-keyword">Any</span>)</span></span> {
            <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> credentials = textFields(), signupButton.isEnabled <span class="hljs-keyword">else</span> {
                <span class="hljs-keyword">return</span>
            }

            <span class="hljs-type">ApiService</span>.shared.signup(credentials: credentials) { token, error <span class="hljs-keyword">in</span>
                <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> token = token, error == <span class="hljs-literal">nil</span> <span class="hljs-keyword">else</span> {
                    <span class="hljs-keyword">return</span> <span class="hljs-type">StatusBarNotificationBanner</span>(title: <span class="hljs-string">"Signup failed. Try again."</span>, style: .danger).show()
                }

                <span class="hljs-type">AuthService</span>.shared.saveToken(token).then {
                    <span class="hljs-keyword">self</span>.closeButtonWasPressed()
                }
            }
        }

        <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">textFields</span><span class="hljs-params">()</span></span> -> <span class="hljs-type">AuthService</span>.<span class="hljs-type">SignupCredentials</span>? {
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> name = nameTextField.text, <span class="hljs-keyword">let</span> email = emailTextField.text, <span class="hljs-keyword">let</span> pass = passwordTextfield.text {
                <span class="hljs-keyword">return</span> (name, email, pass)
            }

            <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
        }

        <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">activateSignupButtonIfNecessary</span><span class="hljs-params">()</span></span> {
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> field = textFields() {
                signupButton.isEnabled = !field.name.isEmpty && !field.email.isEmpty && !field.password.isEmpty
            }
        }

        <span class="hljs-meta">@objc</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">textFieldChanged</span><span class="hljs-params">(<span class="hljs-number">_</span> sender: UITextField)</span></span> {
            activateSignupButtonIfNecessary()
        }
    }
```

Set the controller as the custom class for the signup storyboard scene.

Above we have three `@IBOutlet`'s for our signup text fields and one `@IBOutlet` for our signup button. In the `viewDidLoad` method we add a callback for our text fields to be triggered when the text is changed. We also call the `activateSignupButtonIfNecessary` method, which activates the signup button if all the field’s contents are valid.

We have two `@IBAction` functions. The first for when the close button is pressed and the other for when the signup button is pressed. When the **Sign up** button is pressed, the `signupButtonWasPressed` method is called, which uses the `ApiService` to create an account for the user and log the user in. If the signup fails we use the [NotificationBanner](https://github.com/Daltron/NotificationBanner) package to display an error.

We also have other helper methods. The `textFields` method returns a tuple of the text fields contents and the `textFieldChanged` method is fired every time a text field’s content is modified.

Next, create a `LoginViewController` class and paste the following code into the file:

```
    <span class="hljs-keyword">import</span> UIKit
    <span class="hljs-keyword">import</span> NotificationBannerSwift

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LoginViewController</span>: <span class="hljs-title">UIViewController</span> </span>{
        <span class="hljs-meta">@IBOutlet</span> <span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> emailTextField: <span class="hljs-type">UITextField</span>!
        <span class="hljs-meta">@IBOutlet</span> <span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> passwordTextField: <span class="hljs-type">UITextField</span>!
        <span class="hljs-meta">@IBOutlet</span> <span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> loginButton: <span class="hljs-type">UIBarButtonItem</span>!

        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">viewDidLoad</span><span class="hljs-params">()</span></span> {
            <span class="hljs-keyword">super</span>.viewDidLoad()

            activateLoginButtonIfNecessary()

            emailTextField.addTarget(<span class="hljs-keyword">self</span>, action: #selector(textFieldChanged(<span class="hljs-number">_</span>:)), <span class="hljs-keyword">for</span>: .editingChanged)
            passwordTextField.addTarget(<span class="hljs-keyword">self</span>, action: #selector(textFieldChanged(<span class="hljs-number">_</span>:)), <span class="hljs-keyword">for</span>: .editingChanged)
        }

        <span class="hljs-meta">@IBAction</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">closeButtonWasPressed</span><span class="hljs-params">(<span class="hljs-number">_</span> sender: <span class="hljs-keyword">Any</span>? = <span class="hljs-literal">nil</span>)</span></span> {
            dismiss(animated: <span class="hljs-literal">true</span>, completion: <span class="hljs-literal">nil</span>)
        }

        <span class="hljs-meta">@IBAction</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">loginButtonWasPressed</span><span class="hljs-params">(<span class="hljs-number">_</span> sender: <span class="hljs-keyword">Any</span>)</span></span> {
            <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> credentials = textFields(), loginButton.isEnabled <span class="hljs-keyword">else</span> {
                <span class="hljs-keyword">return</span>
            }

            <span class="hljs-type">ApiService</span>.shared.login(credentials: credentials) { token, error <span class="hljs-keyword">in</span>
                <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> token = token, error == <span class="hljs-literal">nil</span> <span class="hljs-keyword">else</span> {
                    <span class="hljs-keyword">return</span> <span class="hljs-type">StatusBarNotificationBanner</span>(title: <span class="hljs-string">"Login failed, try again."</span>, style: .danger).show()
                }

                <span class="hljs-type">AuthService</span>.shared.saveToken(token).then {
                    <span class="hljs-keyword">self</span>.closeButtonWasPressed()
                }
            }
        }

        <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">textFields</span><span class="hljs-params">()</span></span> -> <span class="hljs-type">AuthService</span>.<span class="hljs-type">LoginCredentials</span>? {
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> email = emailTextField.text, <span class="hljs-keyword">let</span> password = passwordTextField.text {
                <span class="hljs-keyword">return</span> (email, password)
            }

            <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
        }

        <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">activateLoginButtonIfNecessary</span><span class="hljs-params">()</span></span> {
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> field = textFields() {
                loginButton.isEnabled = !field.email.isEmpty && !field.password.isEmpty
            }
        }

        <span class="hljs-meta">@objc</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">textFieldChanged</span><span class="hljs-params">(<span class="hljs-number">_</span> sender: UITextField)</span></span> {
            activateLoginButtonIfNecessary()
        }
    }
```

Set the controller as the custom class for the login storyboard scene.

The controller above functions very similarly to the `SignupViewController`. When the `loginButtonWasPressed` method is called it uses the `ApiService` to log the user in and save the token.

Next, we need to create the settings controller. This will be where the settings can be managed. Create a `SettingsTableViewController` and paste the following code into the file:

```
    <span class="hljs-keyword">import</span> UIKit

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SettingsTableViewController</span>: <span class="hljs-title">UITableViewController</span> </span>{
        <span class="hljs-keyword">let</span> settings = {
            <span class="hljs-keyword">return</span> <span class="hljs-type">SettingsService</span>.shared.settings
        }()

        <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">shouldCheckCell</span><span class="hljs-params">(at index: IndexPath, with setting: String)</span></span> -> <span class="hljs-type">Bool</span> {
            <span class="hljs-keyword">let</span> status = <span class="hljs-type">Setting</span>.<span class="hljs-type">Notification</span>.<span class="hljs-type">Comments</span>(rawValue: setting)

            <span class="hljs-keyword">return</span> (status == .off && index.row == <span class="hljs-number">0</span>) ||
                   (status == .following && index.row == <span class="hljs-number">1</span>) ||
                   (status == .everyone && index.row == <span class="hljs-number">2</span>)
        }

        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">tableView</span><span class="hljs-params">(<span class="hljs-number">_</span> tableView: UITableView, cellForRowAt indexPath: IndexPath)</span></span> -> <span class="hljs-type">UITableViewCell</span> {
            <span class="hljs-keyword">let</span> cell = <span class="hljs-keyword">super</span>.tableView(tableView, cellForRowAt: indexPath)
            cell.accessoryType = .<span class="hljs-keyword">none</span>

            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> setting = settings[<span class="hljs-string">"notification_comments"</span>], shouldCheckCell(at: indexPath, with: setting) {
                cell.accessoryType = .checkmark
            }

            <span class="hljs-keyword">return</span> cell
        }

        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">tableView</span><span class="hljs-params">(<span class="hljs-number">_</span> tableView: UITableView, didSelectRowAt indexPath: IndexPath)</span></span> {
            <span class="hljs-keyword">let</span> rowsCount = <span class="hljs-keyword">self</span>.tableView.numberOfRows(inSection: indexPath.section)

            <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..<rowsCount  {
                <span class="hljs-keyword">let</span>  rowIndexPath = <span class="hljs-type">IndexPath</span>(row: i, section: indexPath.section)

                <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> cell = <span class="hljs-keyword">self</span>.tableView.cellForRow(at: rowIndexPath) {
                    cell.accessoryType = indexPath.row == i ? .checkmark : .<span class="hljs-keyword">none</span>
                }
            }

            <span class="hljs-keyword">let</span> setting = indexPath.row == <span class="hljs-number">0</span> ? <span class="hljs-string">"Off"</span> : (indexPath.row == <span class="hljs-number">1</span> ? <span class="hljs-string">"Following"</span> : <span class="hljs-string">"Everyone"</span>)

            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> status = <span class="hljs-type">Setting</span>.<span class="hljs-type">Notification</span>.<span class="hljs-type">Comments</span>(rawValue: setting) {
                <span class="hljs-type">SettingsService</span>.shared.updateCommentsNotificationSetting(status)
            }
        }
    }
```

Set the controller as the custom class for the settings storyboard scene.

In the `SettingsTableViewController`, we load the settings from the `SettingsService` class, which we will create later. We then define a `shouldCheckCell` method, which will determine if the cell row should be checked by checking the users setting.

![](//images.ctfassets.net/1es3ne0caaid/4TH2I2tOQ82eAIcueuEoqC/4597c23c2705aaf92044a0dc69a08c70/ios-push-notifications-social-network-settings.png)

As seen from the storyboard scene, there are three possible settings for the comments notification section: ‘**Off**’, ‘**From people I follow**’ and ‘**From everyone**’. The settings controller attempts to update the setting locally and remotely using the `SettingsService` when the setting is changed.

Next, create the `SearchTableViewController` and paste the following code into it:

```
    <span class="hljs-keyword">import</span> UIKit
    <span class="hljs-keyword">import</span> NotificationBannerSwift

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SearchTableViewController</span>: <span class="hljs-title">UITableViewController</span> </span>{

        <span class="hljs-keyword">var</span> users: <span class="hljs-type">Users</span> = []

        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">viewDidLoad</span><span class="hljs-params">()</span></span> {
            <span class="hljs-keyword">super</span>.viewDidLoad()

            <span class="hljs-type">ApiService</span>.shared.fetchUsers { users <span class="hljs-keyword">in</span>
                <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> users = users <span class="hljs-keyword">else</span> {
                    <span class="hljs-keyword">return</span> <span class="hljs-type">StatusBarNotificationBanner</span>(title: <span class="hljs-string">"Unable to fetch users."</span>, style: .danger).show()
                }

                <span class="hljs-keyword">self</span>.users = users
                <span class="hljs-keyword">self</span>.tableView.reloadData()
            }
        }

        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">tableView</span><span class="hljs-params">(<span class="hljs-number">_</span> tableView: UITableView, numberOfRowsInSection section: Int)</span></span> -> <span class="hljs-type">Int</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">self</span>.users.<span class="hljs-built_in">count</span>
        }

        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">tableView</span><span class="hljs-params">(<span class="hljs-number">_</span> tableView: UITableView, cellForRowAt indexPath: IndexPath)</span></span> -> <span class="hljs-type">UITableViewCell</span> {
            <span class="hljs-keyword">let</span> user = <span class="hljs-keyword">self</span>.users[indexPath.row]
            <span class="hljs-keyword">let</span> cell = tableView.dequeueReusableCell(withIdentifier: <span class="hljs-string">"User"</span>, <span class="hljs-keyword">for</span>: indexPath) <span class="hljs-keyword">as</span>! <span class="hljs-type">UserListTableViewCell</span>

            cell.delegate = <span class="hljs-keyword">self</span>
            cell.indexPath = indexPath
            cell.textLabel?.text = user.name

            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> following = user.follows {
                cell.setFollowStatus(following)
            }

            <span class="hljs-keyword">return</span> cell
        }

    }

    <span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">SearchTableViewController</span>: <span class="hljs-title">UserListCellFollowButtonDelegate</span> </span>{

        <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">followButtonTapped</span><span class="hljs-params">(at indexPath: IndexPath)</span></span> {
            <span class="hljs-keyword">let</span> user = <span class="hljs-keyword">self</span>.users[indexPath.row]
            <span class="hljs-keyword">let</span> userFollows = user.follows ?? <span class="hljs-literal">false</span>

            <span class="hljs-type">ApiService</span>.shared.toggleFollowStatus(forUserId: user.id, following: userFollows) { successful <span class="hljs-keyword">in</span>
                <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> successful = successful, successful <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> }

                <span class="hljs-keyword">self</span>.users[indexPath.row].follows = !userFollows
                <span class="hljs-keyword">self</span>.tableView.reloadData()
            }
        }

    }
```

Set the controller as the custom class for the search storyboard scene.

Though we have named the class `SearchTableViewController` we are actually not going to be doing any searches. We are going to have a make-believe search result, which will display the list of users on the service with a **Follow/Unfollow** button to make it easy to follow or unfollow a user.

In the `viewDidLoad` method we call the `fetchUsers` method on the `ApiService` class and then we load the users to the `users` property, which is then used as the table’s data. In the class extension, we implement the `UserListCellFollowButtonDelegate` protocol, which makes it easy for us to know when the **Follow/Unfollow** button is tapped. We use the [delegation pattern](https://www.appcoda.com/swift-delegate/) to make this possible.

Next, create the `TimelineTableViewController` class and paste the following code into it:

```
    <span class="hljs-keyword">import</span> UIKit
    <span class="hljs-keyword">import</span> Alamofire
    <span class="hljs-keyword">import</span> NotificationBannerSwift
    <span class="hljs-keyword">import</span> PushNotifications

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TimelineTableViewController</span>: <span class="hljs-title">UITableViewController</span> </span>{
        <span class="hljs-keyword">var</span> photos: <span class="hljs-type">Photos</span> = []
        <span class="hljs-keyword">var</span> selectedPhoto: <span class="hljs-type">Photo</span>?
        <span class="hljs-keyword">let</span> picker = <span class="hljs-type">UIImagePickerController</span>()

        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">viewDidLoad</span><span class="hljs-params">()</span></span> {
            <span class="hljs-keyword">super</span>.viewDidLoad()
            <span class="hljs-keyword">self</span>.reloadButtonWasPressed()
            <span class="hljs-keyword">self</span>.picker.delegate = <span class="hljs-keyword">self</span>
        }

        <span class="hljs-meta">@IBAction</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">userButtonWasPressed</span><span class="hljs-params">(<span class="hljs-number">_</span> sender: <span class="hljs-keyword">Any</span>)</span></span> {
            <span class="hljs-type">AuthService</span>.shared.logout()
            dismiss(animated: <span class="hljs-literal">true</span>, completion: <span class="hljs-literal">nil</span>)
        }

        <span class="hljs-meta">@IBAction</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">reloadButtonWasPressed</span><span class="hljs-params">(<span class="hljs-number">_</span> sender: <span class="hljs-keyword">Any</span>? = <span class="hljs-literal">nil</span>)</span></span> {
            <span class="hljs-type">ApiService</span>.shared.fetchPosts { photos <span class="hljs-keyword">in</span>
                <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> photos = photos {
                    <span class="hljs-keyword">self</span>.photos = photos
                    <span class="hljs-keyword">self</span>.tableView.reloadData()
                }
            }
        }

        <span class="hljs-meta">@IBAction</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">addButtonWasPressed</span><span class="hljs-params">(<span class="hljs-number">_</span> sender: <span class="hljs-keyword">Any</span>)</span></span> {
            picker.sourceType = .photoLibrary
            picker.mediaTypes = <span class="hljs-type">UIImagePickerController</span>.availableMediaTypes(<span class="hljs-keyword">for</span>: .photoLibrary)!
            picker.modalPresentationStyle = .popover
            picker.popoverPresentationController?.barButtonItem = <span class="hljs-literal">nil</span>
            present(picker, animated: <span class="hljs-literal">true</span>, completion: <span class="hljs-literal">nil</span>)
        }

        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">prepare</span><span class="hljs-params">(<span class="hljs-keyword">for</span> segue: UIStoryboardSegue, sender: <span class="hljs-keyword">Any</span>?)</span></span> {
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> vc = segue.destination <span class="hljs-keyword">as</span>? <span class="hljs-type">CommentsTableViewController</span>, <span class="hljs-keyword">let</span> photo = selectedPhoto {
                selectedPhoto = <span class="hljs-literal">nil</span>
                vc.photoId = photo.id
                vc.comments = photo.comments
            }
        } 
    }
```

Set the controller as the custom class for the timeline storyboard scene.

In the controller above we have the `photos` property, which is an array of all the photos on the service, the `selectedPhoto`, which will temporarily hold the selected photo object, and the `picker` property, which we will use for the image picker when trying to upload images to the service.

 In the `viewDidLoad` method, we load the posts by calling the `reloadButtonWasPressed` method, then we set the class as the `picker.delegate`. We have the `@IBAction` method `addButtonWasPressed`, which launches the iOS image picker.

 The `prepare` method is called automatically when the controller is navigating to the comments controller. So in here, we set the comments to the comments controller so we have something to display immediately. We also set the `photoId` to the comments controller.

Next, in the same class, paste the following at the bottom:

```
    <span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">TimelineTableViewController</span> </span>{

        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">tableView</span><span class="hljs-params">(<span class="hljs-number">_</span> tableView: UITableView, numberOfRowsInSection section: Int)</span></span> -> <span class="hljs-type">Int</span> {
            <span class="hljs-keyword">return</span> photos.<span class="hljs-built_in">count</span>
        }

        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">tableView</span><span class="hljs-params">(<span class="hljs-number">_</span> tableView: UITableView, cellForRowAt indexPath: IndexPath)</span></span> -> <span class="hljs-type">UITableViewCell</span> {
            <span class="hljs-keyword">let</span> photo = photos[indexPath.row]
            <span class="hljs-keyword">let</span> cell = tableView.dequeueReusableCell(withIdentifier: <span class="hljs-string">"PhotoCell"</span>, <span class="hljs-keyword">for</span>: indexPath) <span class="hljs-keyword">as</span>! <span class="hljs-type">PhotoListTableViewCell</span>

            cell.delegate = <span class="hljs-keyword">self</span>
            cell.indexPath = indexPath
            cell.nameLabel.text = photo.user.name
            cell.photo.image = <span class="hljs-type">UIImage</span>(named: <span class="hljs-string">"loading"</span>)

            <span class="hljs-type">Alamofire</span>.request(photo.image).responseData { response <span class="hljs-keyword">in</span>
                <span class="hljs-keyword">if</span> response.error == <span class="hljs-literal">nil</span>, <span class="hljs-keyword">let</span> data = response.data {
                    cell.photo.image = <span class="hljs-type">UIImage</span>(data: data)
                }
            }

            <span class="hljs-keyword">return</span> cell
        } 

    }

    <span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">TimelineTableViewController</span>: <span class="hljs-title">PhotoListCellDelegate</span> </span>{

        <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">commentButtonWasTapped</span><span class="hljs-params">(at indexPath: IndexPath)</span></span> {
            <span class="hljs-keyword">self</span>.selectedPhoto = photos[indexPath.row]
            <span class="hljs-keyword">self</span>.performSegue(withIdentifier: <span class="hljs-string">"Comments"</span>, sender: <span class="hljs-keyword">self</span>)
        }

    }
```

In the code above, we have two extensions for the `TimelineTableViewController`. The first extension defines how we want to present the photos to the table view. The second extension is an implementation of the `PhotoListCellDelegate`, which is another implementation of the delegation pattern. The method defined here, `commentButtonWasTapped`, will be triggered when the Comment button is pressed on a photo cell.

In the same file add the last class extension at the bottom of the file:

```
    <span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">TimelineTableViewController</span>: <span class="hljs-title">UIImagePickerControllerDelegate</span>, <span class="hljs-title">UINavigationControllerDelegate</span> </span>{

        <span class="hljs-meta">@objc</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">imagePickerController</span><span class="hljs-params">(<span class="hljs-number">_</span> picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : <span class="hljs-keyword">Any</span>])</span></span> {
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> selected = info[<span class="hljs-string">"UIImagePickerControllerOriginalImage"</span>] <span class="hljs-keyword">as</span>? <span class="hljs-type">UIImage</span> {
                <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> image = <span class="hljs-type">UIImageJPEGRepresentation</span>(selected, <span class="hljs-number">0</span>) <span class="hljs-keyword">else</span> { 
                    <span class="hljs-keyword">return</span> 
                }

                <span class="hljs-keyword">let</span> uploadPhotoHandler: (() -> <span class="hljs-type">Void</span>)? = {
                    <span class="hljs-keyword">var</span> caption: <span class="hljs-type">UITextField</span>?

                    <span class="hljs-keyword">let</span> alert = <span class="hljs-type">UIAlertController</span>(title: <span class="hljs-string">"Add Caption"</span>, message: <span class="hljs-literal">nil</span>, preferredStyle: .alert)
                    alert.addTextField(configurationHandler: { textfield <span class="hljs-keyword">in</span> caption = textfield })
                    alert.addAction(<span class="hljs-type">UIAlertAction</span>(title: <span class="hljs-string">"Cancel"</span>, style: .cancel, handler: <span class="hljs-literal">nil</span>))
                    alert.addAction(<span class="hljs-type">UIAlertAction</span>(title: <span class="hljs-string">"Save"</span>, style: .<span class="hljs-keyword">default</span>, handler: { action <span class="hljs-keyword">in</span>
                        <span class="hljs-keyword">var</span> filename = <span class="hljs-string">"upload.jpg"</span>
                        <span class="hljs-keyword">let</span> caption = caption?.text ?? <span class="hljs-string">"No caption"</span>

                        <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> url = info[<span class="hljs-type">UIImagePickerControllerImageURL</span>] <span class="hljs-keyword">as</span>? <span class="hljs-type">NSURL</span>, <span class="hljs-keyword">let</span> name = url.lastPathComponent {
                            filename = name
                        }

                        <span class="hljs-type">ApiService</span>.shared.uploadImage(image, caption: caption, name: filename) { photo, error <span class="hljs-keyword">in</span>
                            <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> photo = photo, error == <span class="hljs-literal">nil</span> <span class="hljs-keyword">else</span> {
                                <span class="hljs-keyword">return</span> <span class="hljs-type">StatusBarNotificationBanner</span>(title: <span class="hljs-string">"Failed to upload image"</span>, style: .danger).show()
                            }

                            <span class="hljs-keyword">try</span>? <span class="hljs-type">PushNotifications</span>.shared.subscribe(interest: <span class="hljs-string">"photo_\(photo.id)-comment_following"</span>)
                            <span class="hljs-keyword">try</span>? <span class="hljs-type">PushNotifications</span>.shared.subscribe(interest: <span class="hljs-string">"photo_\(photo.id)-comment_everyone"</span>)

                            <span class="hljs-keyword">self</span>.photos.insert(photo, at: <span class="hljs-number">0</span>)
                            <span class="hljs-keyword">self</span>.tableView.reloadData()

                            <span class="hljs-type">StatusBarNotificationBanner</span>(title: <span class="hljs-string">"Uploaded successfully"</span>, style: .success).show()
                        }
                    }))

                    <span class="hljs-keyword">self</span>.present(alert, animated: <span class="hljs-literal">true</span>, completion: <span class="hljs-literal">nil</span>)
                }

                <span class="hljs-keyword">self</span>.dismiss(animated: <span class="hljs-literal">true</span>, completion: uploadPhotoHandler)
            }
        }

    }
```

In the extension above, we implement the `UIImagePickerControllerDelegate`, which let’s us handle image selection from the `UIImagePickerController`. When an image is selected, the method above will be called.

We handle it by getting the selected image, displaying an alert controller with a text field so we can get a caption for the image and then we send the image and the caption to the API using the `ApiService`.

When the upload is complete, we add the newly added photo to the table and then we subscribe the user to the Pusher Beam Interest so they can receive push notifications when comments are made to the photo.

Also above we subscribed to two interests. The first is `photo_\(id)-comment_following` and the second one is `photo_\(id)-comment_everyone`. We do this so that we can segment notifications depending on the users setting. On the server, when a comment is added, if the photo owner sets the comment notification setting to following then the push notification will be published to the `photo_\(id)-comment_following` interest.

Next, create the `CommentsTableViewController` class and paste the following code into it:

```
    <span class="hljs-keyword">import</span> UIKit
    <span class="hljs-keyword">import</span> NotificationBannerSwift

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CommentsTableViewController</span>: <span class="hljs-title">UITableViewController</span> </span>{
        <span class="hljs-keyword">var</span> photoId: <span class="hljs-type">Int</span> = <span class="hljs-number">0</span>
        <span class="hljs-keyword">var</span> commentField: <span class="hljs-type">UITextField</span>?
        <span class="hljs-keyword">var</span> comments: <span class="hljs-type">PhotoComments</span> = []

        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">viewDidLoad</span><span class="hljs-params">()</span></span> {
            <span class="hljs-keyword">super</span>.viewDidLoad()

            navigationItem.title = <span class="hljs-string">"Comments"</span>
            navigationController?.navigationBar.prefersLargeTitles = <span class="hljs-literal">false</span>
            navigationItem.rightBarButtonItem = <span class="hljs-type">UIBarButtonItem</span>(title: <span class="hljs-string">"Add"</span>, style: .plain, target: <span class="hljs-keyword">self</span>, action: #selector(addCommentButtonWasTapped))

            <span class="hljs-keyword">if</span> photoId != <span class="hljs-number">0</span> {
                <span class="hljs-type">ApiService</span>.shared.fetchComments(forPhoto: photoId) { comments <span class="hljs-keyword">in</span>
                    <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> comments = comments <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> }

                    <span class="hljs-keyword">self</span>.comments = comments
                    <span class="hljs-keyword">self</span>.tableView.reloadData()
                }
            }
        }

        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">tableView</span><span class="hljs-params">(<span class="hljs-number">_</span> tableView: UITableView, numberOfRowsInSection section: Int)</span></span> -> <span class="hljs-type">Int</span> {
            <span class="hljs-keyword">return</span> comments.<span class="hljs-built_in">count</span>
        }

        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">tableView</span><span class="hljs-params">(<span class="hljs-number">_</span> tableView: UITableView, cellForRowAt indexPath: IndexPath)</span></span> -> <span class="hljs-type">UITableViewCell</span> {
            <span class="hljs-keyword">let</span> cell = tableView.dequeueReusableCell(withIdentifier: <span class="hljs-string">"Comment"</span>, <span class="hljs-keyword">for</span>: indexPath) <span class="hljs-keyword">as</span>! <span class="hljs-type">CommentsListTableViewCell</span>
            <span class="hljs-keyword">let</span> comment = comments[indexPath.row]

            cell.username?.text = comment.user.name
            cell.comment?.text = comment.comment

            <span class="hljs-keyword">return</span> cell
        }

        <span class="hljs-meta">@objc</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">addCommentButtonWasTapped</span><span class="hljs-params">()</span></span> {
            <span class="hljs-keyword">let</span> alertCtrl = <span class="hljs-type">UIAlertController</span>(title: <span class="hljs-string">"Add Comment"</span>, message: <span class="hljs-literal">nil</span>, preferredStyle: .alert)
            alertCtrl.addAction(<span class="hljs-type">UIAlertAction</span>(title: <span class="hljs-string">"Cancel"</span>, style: .cancel, handler: <span class="hljs-literal">nil</span>))
            alertCtrl.addTextField { textField <span class="hljs-keyword">in</span> <span class="hljs-keyword">self</span>.commentField = textField }
            alertCtrl.addAction(<span class="hljs-type">UIAlertAction</span>(title: <span class="hljs-string">"Add Comment"</span>, style: .<span class="hljs-keyword">default</span>) { <span class="hljs-number">_</span> <span class="hljs-keyword">in</span>
                <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> comment = <span class="hljs-keyword">self</span>.commentField?.text <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> }

                <span class="hljs-type">ApiService</span>.shared.leaveComment(forId: <span class="hljs-keyword">self</span>.photoId, comment: comment) { newComment <span class="hljs-keyword">in</span>
                    <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> comment = newComment <span class="hljs-keyword">else</span> {
                        <span class="hljs-keyword">return</span> <span class="hljs-type">StatusBarNotificationBanner</span>(title: <span class="hljs-string">"Failed to post comment"</span>, style: .danger).show()
                    }

                    <span class="hljs-keyword">self</span>.comments.insert(comment, at: <span class="hljs-number">0</span>)
                    <span class="hljs-keyword">self</span>.tableView.reloadData()
                }
            })

            <span class="hljs-keyword">self</span>.present(alertCtrl, animated: <span class="hljs-literal">true</span>, completion: <span class="hljs-literal">nil</span>)
        }
    }
```

Set the controller as the custom class for the timeline storyboard scene.

In the `CommentsTableViewController` above we have the `comments` property, which holds all the comments for the photo, the `photoId` property, which holds the ID of the photo whose comments are being loaded and the `commentField` property, which is the text field that holds new comments.

In the `viewDidLoad` method we set up the controller title and add an ‘**Add**’ button to the right of the navigation bar. Next, we call the `fetchComments` method in the `ApiService` to load comments for the photo.

We have the `addCommentButtonWasTapped` method in the controller, which is activated when the ‘**Add**’ button on the navigation bar is pressed. This brings up an alert controller with a text field where we can get the comment text and then send the comment to the API using the `ApiService`.

### Creating our custom view classes

Since we have created the controllers, let’s create some custom view classes that we need for the cells we used in the controllers earlier.

The first custom cell we will create will be the `PhotoListTableViewCell` class. Create the class and paste the following code into the file:

```
    <span class="hljs-keyword">import</span> UIKit

    <span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">PhotoListCellDelegate</span> </span>{
        <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">commentButtonWasTapped</span><span class="hljs-params">(at indexPath: IndexPath)</span></span>
    }

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PhotoListTableViewCell</span>: <span class="hljs-title">UITableViewCell</span> </span>{
        <span class="hljs-meta">@IBOutlet</span> <span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> nameLabel: <span class="hljs-type">UILabel</span>!
        <span class="hljs-meta">@IBOutlet</span> <span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> photo: <span class="hljs-type">UIImageView</span>!
        <span class="hljs-meta">@IBOutlet</span> <span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> commentButton: <span class="hljs-type">UIButton</span>!

        <span class="hljs-keyword">var</span> indexPath: <span class="hljs-type">IndexPath</span>?    
        <span class="hljs-keyword">var</span> delegate: <span class="hljs-type">PhotoListCellDelegate</span>?

        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">awakeFromNib</span><span class="hljs-params">()</span></span> {
            <span class="hljs-keyword">super</span>.awakeFromNib()
            <span class="hljs-keyword">self</span>.selectionStyle = .<span class="hljs-keyword">none</span>

            commentButton.addTarget(<span class="hljs-keyword">self</span>, action: #selector(commentButtonWasTapped), <span class="hljs-keyword">for</span>: .touchUpInside)
        }

        <span class="hljs-meta">@objc</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">commentButtonWasTapped</span><span class="hljs-params">()</span></span> {
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> indexPath = indexPath, <span class="hljs-keyword">let</span> delegate = delegate {
                delegate.commentButtonWasTapped(at: indexPath)
            }
        }
    }
```

Set this class as the custom class for the cell in the timeline scene of the storyboard.

In the class above we have a few `@IBOutlet`'s for the name, photo and comment button. We have a `commentButtonWasTapped` method that fires the `commentWasTapped` method on a delegate of the cell.

The next cell we want to create is the `CommentsListTableViewCell`. Create the class and paste the following code into the file:

```
    <span class="hljs-keyword">import</span> UIKit

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CommentsListTableViewCell</span>: <span class="hljs-title">UITableViewCell</span> </span>{
        <span class="hljs-meta">@IBOutlet</span> <span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> username: <span class="hljs-type">UILabel</span>!
        <span class="hljs-meta">@IBOutlet</span> <span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> comment: <span class="hljs-type">UILabel</span>!
    }
```

Set this class as the custom class for the cell in the comments scene of the storyboard.

The next cell we want to create is the `UsersListTableViewCell`. Create the class and paste the following code into the file:

```
    <span class="hljs-keyword">import</span> UIKit

    <span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">UserListCellFollowButtonDelegate</span> </span>{
        <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">followButtonTapped</span><span class="hljs-params">(at index:IndexPath)</span></span>
    }

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserListTableViewCell</span>: <span class="hljs-title">UITableViewCell</span> </span>{
        <span class="hljs-keyword">var</span> indexPath: <span class="hljs-type">IndexPath</span>?    
        <span class="hljs-keyword">var</span> delegate: <span class="hljs-type">UserListCellFollowButtonDelegate</span>?

        <span class="hljs-meta">@IBOutlet</span> <span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> followButton: <span class="hljs-type">UIButton</span>!

        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">awakeFromNib</span><span class="hljs-params">()</span></span> {
            <span class="hljs-keyword">super</span>.awakeFromNib()
            <span class="hljs-keyword">self</span>.selectionStyle = .<span class="hljs-keyword">none</span>

            <span class="hljs-keyword">self</span>.setFollowStatus(<span class="hljs-literal">false</span>)
            <span class="hljs-keyword">self</span>.followButton.layer.cornerRadius = <span class="hljs-number">5</span>
            <span class="hljs-keyword">self</span>.followButton.setTitleColor(<span class="hljs-type">UIColor</span>.white, <span class="hljs-keyword">for</span>: .normal)
            <span class="hljs-keyword">self</span>.followButton.addTarget(<span class="hljs-keyword">self</span>, action: #selector(followButtonTapped(<span class="hljs-number">_</span>:)), <span class="hljs-keyword">for</span>: .touchUpInside)
        }

        <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">setFollowStatus</span><span class="hljs-params">(<span class="hljs-number">_</span> following: Bool)</span></span> {
            <span class="hljs-keyword">self</span>.followButton.backgroundColor = following ? <span class="hljs-type">UIColor</span>.red : <span class="hljs-type">UIColor</span>.blue
            <span class="hljs-keyword">self</span>.followButton.setTitle(following ? <span class="hljs-string">"Unfollow"</span> : <span class="hljs-string">"Follow"</span>, <span class="hljs-keyword">for</span>: .normal)
        }

        <span class="hljs-meta">@objc</span> <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">followButtonTapped</span><span class="hljs-params">(<span class="hljs-number">_</span> sender: UIButton)</span></span> {
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> delegate = delegate, <span class="hljs-keyword">let</span> indexPath = indexPath {
                delegate.followButtonTapped(at: indexPath)
            }
        }
    }
```

Set this class as the custom class for the cell in the search scene in the storyboard.

In the class above we have a custom cell to display a user’s name and a follow button. We have a `setFollowStatus` method that toggles the state of the follow button and we have a `followButtonTapped` method that calls the `followButtonTapped` method on a delegate of the cell.

That’s all for custom cell classes. Let’s move on to creating other classes and setting up push notification.

### Adding other classes and setting up push notifications

We still need to create one last file. Create an `AppConstants` file and paste the following code into the file:

```
    <span class="hljs-keyword">import</span> Foundation

    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">AppConstants</span> </span>{
        <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> <span class="hljs-type">API_URL</span> = <span class="hljs-string">"http://127.0.0.1:8000"</span>
        <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> <span class="hljs-type">API_CLIENT_ID</span> = <span class="hljs-string">"API_CLIENT_ID"</span>
        <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> <span class="hljs-type">API_CLIENT_SECRET</span> = <span class="hljs-string">"API_CLIENT_SECRET"</span>
        <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> <span class="hljs-type">PUSHER_INSTANCE_ID</span> = <span class="hljs-string">"PUSHER_INSTANCE_ID
    }</span>
```

In the struct above we have some constants that we will be using throughout the application. These will be used to store application credentials and will be unchanged throughout the lifetime of the application.

> 💡 Replace the key values with the actual values gotten from your Passport installation and from your Pusher dashboard.

Next, open the `AppDelegate` class and replace the contents with the following:

```
    <span class="hljs-keyword">import</span> UIKit
    <span class="hljs-keyword">import</span> PushNotifications

    <span class="hljs-meta">@UIApplicationMain</span>
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AppDelegate</span>: <span class="hljs-title">UIResponder</span>, <span class="hljs-title">UIApplicationDelegate</span> </span>{

        <span class="hljs-keyword">var</span> window: <span class="hljs-type">UIWindow</span>?

        <span class="hljs-keyword">let</span> pushNotifications = <span class="hljs-type">PushNotifications</span>.shared

        <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">application</span><span class="hljs-params">(<span class="hljs-number">_</span> application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: <span class="hljs-keyword">Any</span>]?)</span></span> -> <span class="hljs-type">Bool</span> {
            <span class="hljs-keyword">self</span>.pushNotifications.start(instanceId: <span class="hljs-type">AppConstants</span>.<span class="hljs-type">PUSHER_INSTANCE_ID</span>)
            <span class="hljs-keyword">self</span>.pushNotifications.registerForRemoteNotifications()

            <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>
        }

        <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">application</span><span class="hljs-params">(<span class="hljs-number">_</span> application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data)</span></span> {
            <span class="hljs-keyword">self</span>.pushNotifications.registerDeviceToken(deviceToken)
        }
    }
```

In the class above, we use the [Pusher Beams Swift SDK](https://docs.pusher.com/push-notifications/reference/ios) to register the device for push notifications.

That’s all for our application’s code.

### Adding push notifications to our iOS new application

Now that we have completed the logic for the application, let’s enable push notifications on the application in Xcode.

In the project navigator, select your project, and click on the **Capabilities** tab. [Enable Push Notifications](http://help.apple.com/xcode/mac/current/#/devdfd3d04a1) by turning the switch ON.

![](//images.ctfassets.net/1es3ne0caaid/nwh9O0inWCAwwsi82iq4o/efa381b5ae180d746608e151f4559f0e/ios-push-notifications-social-network-enable-push-notifications.png)

This will create an entitlements file in the root of your project. With that, you have provisioned your application to fully receive push notifications.

### Adding rich push notifications

Let’s take it one step further and add rich notifications. We will want to be able to see the photo commented on in the notification received as this can increase engagement.

In Xcode go to ‘File’ > ‘New’ > ‘Target’ and select ‘Notification Service Extension’. Enter the name of the extension and then click proceed. Make sure the extension is added and embedded to the Gram project. We will call our extension **Notification**.

When the target has been created you will see a new `Notification` group (it may be different depending on what you chose to call your extension) with two files in them. Open the `NotificationService` class and replace the `didReceive` method with the method below:

```
    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">didReceive</span><span class="hljs-params">(<span class="hljs-number">_</span> request: UNNotificationRequest, withContentHandler contentHandler: @escaping <span class="hljs-params">(UNNotificationContent)</span></span></span> -> <span class="hljs-type">Void</span>) {
        <span class="hljs-keyword">self</span>.contentHandler = contentHandler
        bestAttemptContent = (request.content.mutableCopy() <span class="hljs-keyword">as</span>? <span class="hljs-type">UNMutableNotificationContent</span>)

        <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">failEarly</span><span class="hljs-params">()</span></span> {
            contentHandler(request.content)
        }

        <span class="hljs-keyword">guard</span>
            <span class="hljs-keyword">let</span> content = (request.content.mutableCopy() <span class="hljs-keyword">as</span>? <span class="hljs-type">UNMutableNotificationContent</span>),
            <span class="hljs-keyword">let</span> apnsData = content.userInfo[<span class="hljs-string">"data"</span>] <span class="hljs-keyword">as</span>? [<span class="hljs-type">String</span>: <span class="hljs-type">Any</span>],
            <span class="hljs-keyword">let</span> photoURL = apnsData[<span class="hljs-string">"attachment-url"</span>] <span class="hljs-keyword">as</span>? <span class="hljs-type">String</span>,
            <span class="hljs-keyword">let</span> attachmentURL = <span class="hljs-type">URL</span>(string: photoURL.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!),
            <span class="hljs-keyword">let</span> imageData = <span class="hljs-keyword">try</span>? <span class="hljs-type">NSData</span>(contentsOf: attachmentURL, options: <span class="hljs-type">NSData</span>.<span class="hljs-type">ReadingOptions</span>()),
            <span class="hljs-keyword">let</span> attachment = <span class="hljs-type">UNNotificationAttachment</span>.create(imageFileIdentifier: <span class="hljs-string">"image.png"</span>, data: imageData, options: <span class="hljs-literal">nil</span>)
            <span class="hljs-keyword">else</span> {
                <span class="hljs-keyword">return</span> failEarly()
        }

        content.attachments = [attachment]
        contentHandler(content.copy() <span class="hljs-keyword">as</span>! <span class="hljs-type">UNNotificationContent</span>)
    }
```

Above we are simply getting the notifications payload and then extracting the data including the `attachment-url`, which is the photo URL. We then create an attachment for the notification and add it to the notification’s content. That’s all we need to do to add the image as an attachment.

> ⚠️ Your image URL has to be a secure URL with HTTPS or iOS will not load the image. You can override this setting in your `info.plist` file but it is strongly recommended that you don’t.

Next, create a new file in the Notification extension called `UNNotificationAttachment.swift` and paste the following into the file:

```
    <span class="hljs-keyword">import</span> Foundation
    <span class="hljs-keyword">import</span> UserNotifications

    <span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">UNNotificationAttachment</span> </span>{

        <span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">create</span><span class="hljs-params">(imageFileIdentifier: String, data: NSData, options: [NSObject : AnyObject]?)</span></span> -> <span class="hljs-type">UNNotificationAttachment</span>? {
            <span class="hljs-keyword">let</span> fileManager = <span class="hljs-type">FileManager</span>.<span class="hljs-keyword">default</span>
            <span class="hljs-keyword">let</span> tmpSubFolderName = <span class="hljs-type">ProcessInfo</span>.processInfo.globallyUniqueString
            <span class="hljs-keyword">let</span> tmpSubFolderURL = <span class="hljs-type">NSURL</span>(fileURLWithPath: <span class="hljs-type">NSTemporaryDirectory</span>()).appendingPathComponent(tmpSubFolderName, isDirectory: <span class="hljs-literal">true</span>)

            <span class="hljs-keyword">do</span> {
                <span class="hljs-keyword">try</span> fileManager.createDirectory(at: tmpSubFolderURL!, withIntermediateDirectories: <span class="hljs-literal">true</span>, attributes: <span class="hljs-literal">nil</span>)
                <span class="hljs-keyword">let</span> fileURL = tmpSubFolderURL?.appendingPathComponent(imageFileIdentifier)
                <span class="hljs-keyword">try</span> data.write(to: fileURL!, options: [])
                <span class="hljs-keyword">let</span> imageAttachment = <span class="hljs-keyword">try</span> <span class="hljs-type">UNNotificationAttachment</span>(identifier: imageFileIdentifier, url: fileURL!, options: options)
                <span class="hljs-keyword">return</span> imageAttachment
            } <span class="hljs-keyword">catch</span> <span class="hljs-keyword">let</span> error {
                <span class="hljs-built_in">print</span>(<span class="hljs-string">"error \(error)"</span>)
            }

            <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
        }
    }
```

The code above is a class extension for the `UNNotificationAttachment` class. The extension contains the `create` method that allows us to create a temporary image to store the image attachment that was sent as a push notification.

Now you can build your application using Xcode. Make sure the Laravel application is running or the app won’t be able to fetch the data.

### Allowing our application to connect locally

If you are going to be testing the app’s backend using a local server, then there is one last thing we need to do. Open the `info.plist` file and add an entry to the `plist` file to allow connection to our local server:

![](//images.ctfassets.net/1es3ne0caaid/5UymGAiLeg22C46SEyay4U/0f66ae00ca879dc03852ece398c955f9/ios-push-notifications-social-network-connect-locally.png)

That’s it now. We can run our application. However, **remember that to demo the push notifications, you will need an actual iOS device as simulators cannot receive push notifications.**

Here is a screen recording of the application in action:

![](//images.ctfassets.net/1es3ne0caaid/51GgQzYiqQEMAQWOK8AMgS/fd0d5bb1bbaa7f1f198fed340d25cd53/ios-push-notifications-social-network-demo.gif)

## Conclusion

In this article, we have seen how you can use Pusher Beams to send push notifications from a Laravel backend and a Swift iOS client application. When creating social networks it is essential that the push notifications we send are relevant and not spammy and Pusher Beams can help with this.

The source code to the application is on [GitHub](https://github.com/neoighodaro-articles/pusher-beams-ios-social-network).

This post first appeared on the [Pusher blog](https://pusher.com/tutorials/social-notifications-ios-part-2).