DEV Community

Neo
Neo

Posted on

Create a cryptocurrency tracking app with push notifications using Swift and Laravel - Part 2: The iOS app

You will need the following installed on your machine: Xcode, the Laravel CLI, SQLite, and Cocoapods. Familiarity with the Xcode IDE will be helpful. You should have completed part one of the series.

In the first part of this article, we started developing our cryptocurrency alert application. We developed the backend of the application that will power the iOS application. As it stands, our backend application can return settings for a device based on its UUID, save the settings for a device based on its UUID and also it can figure out what devices to send push notifications to when the currencies update.

In this part, we will focus on creating the iOS application using Swift and Xcode.

Prerequisites

To follow along you need the following requirements:

What we will be building

We already started out by building the backend of the application using Laravel. So next, we will build the iOS application using Swift. If you want to test the push notifications then you will need to run the application on a live device.

How the client application will work

For the client app, the iOS application, we will create a simple list that will display the available currencies and the current prices to the dollar. Whenever the price of the cryptocurrency changes, we will trigger an event using Pusher Channels so the prices are updated.

From the application, you will be able to set a minimum and maximum price change when you want to be alerted. For instance, you can configure the application to send a push notification to the application when the price of one Etherium (ETH) goes below $500. You can also configure the application to receive a notification when the price of Bitcoin goes above $5000.

How the application will look

When we are done with the application, here's how the application will look:

Let’s get started.

Setting up your client application

Launch Xcode and click Create a new Xcode project. Select Single View App and click Next. Enter your Product Name, we will call our project cryptoalat, and select Swift from the Language options. You can also change any other detail you wish to on the screen then click Next.

Installing dependencies

Now you have your Xcode project. Close Xcode and open a terminal window. cd to the iOS project directory in terminal and run the command below to create a Podfile:

    $ pod init

Enter fullscreen mode Exit fullscreen mode

The Podfile is a specification that describes the dependencies of the targets of one or more Xcode projects. The file should simply be named Podfile. All the examples in the guides are based on CocoaPods version 1.0 and onwards. - Cocoapods Guides

This will generate a new file called Podfile in the root of your project. Open this file in any editor and update the file as seen below:

    // File: Podfile
    platform :ios, '11.0'

    target 'cryptoalat' do
      use_frameworks!

      pod 'Alamofire', '~> 4.7.2'
      pod 'PushNotifications', '~> 0.10.8'
      pod 'PusherSwift', '~> 6.1.0'
      pod 'NotificationBannerSwift', '~> 1.6.3'
    end

Enter fullscreen mode Exit fullscreen mode

If you used a project name other than cryptoalat, then change it in the Podfile to match your project’s target name.

Go to terminal and run the command below to install your dependencies:

    $ pod install

Enter fullscreen mode Exit fullscreen mode

When the installation is complete, you will have a *.xcworkspace file in the root of your project. Open this file in Xcode and let’s start developing our cryptocurrency alert application.

Building the iOS application

Creating our storyboard

The first thing we need to do is design our storyboard for the application. This is what we want the storyboard to look like when we are done.

Open the Main.storyboard file and design as seen above.

Above we have three scenes. The first scene, which is the entry point, is the launch scene. We then draw a manual segue with an identifier called Main. Then we set the segue Kind to Present Modally. This will present the next scene which is a navigation view controller. Navigation controllers already have an attached root view controller by default.

We will use this attached view controller, which is a TableViewController, as the main view for our application. It’ll list the available currencies and show us a text field that allows us to change the setting for that currency when it is tapped.

On the third scene, we set the reuse identifier of the cells to coin and we drag two labels to the prototype cell. The first label will be for the coin name and the second label will be for the price.

Now that we have the scenes, let’s create some controllers and view classes and connect them to our storyboard scenes.

Creating your 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-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-type">SettingsService</span>.shared.loadSettings {
                <span class="hljs-keyword">self</span>.routeToMainController()
            }
        }

        <span class="hljs-keyword">fileprivate</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">routeToMainController</span><span class="hljs-params">()</span></span> {
            performSegue(withIdentifier: <span class="hljs-string">"Main"</span>, sender: <span class="hljs-keyword">self</span>)
        }
    }
Enter fullscreen mode Exit fullscreen mode

Set the controller as the custom class for the first scene in the Main.storyboard file.

In the code, we load the settings using a SettingsService class we will create later. When the settings are loaded for the device, we then call the routeToMainController method, which routes the application to the main controller using the Main segue we created earlier.

The next controller we will be creating will be the CoinsTableViewController. This will be the controller that will be tied to the third scene which is the main scene.

Create the CoinsTableViewController and replace the contents with the following code;

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

    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Coin</span> </span>{
        <span class="hljs-keyword">let</span> name: <span class="hljs-type">String</span>
        <span class="hljs-keyword">let</span> rate: <span class="hljs-type">Float</span>
    }

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

        <span class="hljs-keyword">var</span> coins: [<span class="hljs-type">Coin</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">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">numberOfSections</span><span class="hljs-params">(<span class="hljs-keyword">in</span> tableView: UITableView)</span></span> -> <span class="hljs-type">Int</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-number">1</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> coins.<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> coin = coins[indexPath.row]
            <span class="hljs-keyword">let</span> cell = tableView.dequeueReusableCell(withIdentifier: <span class="hljs-string">"coin"</span>, <span class="hljs-keyword">for</span>: indexPath) <span class="hljs-keyword">as</span>! <span class="hljs-type">CoinTableViewCell</span>

            cell.name.text = <span class="hljs-string">"1 \(coin.name) ="</span>
            cell.amount.text = <span class="hljs-string">"$\(String(coin.rate))"</span>

            <span class="hljs-keyword">return</span> cell
        }
    }
Enter fullscreen mode Exit fullscreen mode

Set the controller as the custom class for the first scene in the Main.storyboard file.

Above we have defined the Coin struct and it has a name and rate property. We have the controller which we define the coins property as an array of Coins. We then have some boilerplate code that comes with creating a table view controller.

The numberOfSections method specifies how many sections the table will have. In the first tableView method, we return the number of coins available and in the second tableView method, we define how we want each row to be handled.

Creating other supporting classes

If you noticed in the code above, we referenced a CoinTableViewCell as the class for each row in the last tableView method. Let’s create that.

Create a CoinTableViewCell class and paste the following code into it:

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CoinTableViewCell</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> name: <span class="hljs-type">UILabel</span>!    
        <span class="hljs-meta">@IBOutlet</span> <span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> amount: <span class="hljs-type">UILabel</span>!
    }
Enter fullscreen mode Exit fullscreen mode

Open the Main.storyboard file and set the class as the custom class for the prototype cell in the third scene of the Main.storyboard file. When you have set the class, connect the @IBOutlets as specified in the cell class above.

The next class we need to create is the SettingsService. This class will be responsible for updating and fetching the settings for the device.

Create a new SettingsService class and replace the contents with the following code:

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

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SettingsService</span> </span>{
        <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> key = <span class="hljs-string">"CryptoAlat"</span>
        <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> shared = <span class="hljs-type">SettingsService</span>()

        <span class="hljs-keyword">var</span> settings: <span class="hljs-type">Settings</span>? {
            <span class="hljs-keyword">get</span> {
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">self</span>.getCachedSettings()
            }
            <span class="hljs-keyword">set</span>(settings) {
                <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> settings = settings {
                    <span class="hljs-keyword">self</span>.updateCachedSettings(settings)
                }
            }
        }

        <span class="hljs-keyword">private</span> <span class="hljs-keyword">init</span>() {}

        <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">loadSettings</span><span class="hljs-params">(completion: @escaping<span class="hljs-params">()</span></span></span> -> <span class="hljs-type">Void</span>) {
            fetchRemoteSettings { settings <span class="hljs-keyword">in</span>
                <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> settings = settings <span class="hljs-keyword">else</span> {
                    <span class="hljs-keyword">return</span> <span class="hljs-keyword">self</span>.saveSettings(<span class="hljs-keyword">self</span>.defaultSettings()) { <span class="hljs-number">_</span> <span class="hljs-keyword">in</span>
                        completion()
                    }
                }

                <span class="hljs-keyword">self</span>.updateCachedSettings(settings)
                completion()
            }
        }

        <span class="hljs-keyword">fileprivate</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">defaultSettings</span><span class="hljs-params">()</span></span> -> <span class="hljs-type">Settings</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-type">Settings</span>(
                btc_min_notify: <span class="hljs-number">0</span>, 
                btc_max_notify: <span class="hljs-number">0</span>, 
                eth_min_notify: <span class="hljs-number">0</span>, 
                eth_max_notify: <span class="hljs-number">0</span>
            )
        }

        <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">saveSettings</span><span class="hljs-params">(<span class="hljs-number">_</span> settings: Settings, completion: @escaping<span class="hljs-params">(Bool)</span></span></span> -> <span class="hljs-type">Void</span>) {
            updateRemoteSettings(settings, completion: { saved <span class="hljs-keyword">in</span>
                <span class="hljs-keyword">if</span> saved {
                    <span class="hljs-keyword">self</span>.updateCachedSettings(settings)
                }

                completion(saved)
            })
        }

        <span class="hljs-keyword">fileprivate</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fetchRemoteSettings</span><span class="hljs-params">(completion: @escaping <span class="hljs-params">(Settings?)</span></span></span> -> <span class="hljs-type">Void</span>) {
            <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> deviceID = <span class="hljs-type">AppConstants</span>.deviceIDFormatted <span class="hljs-keyword">else</span> {
                <span class="hljs-keyword">return</span> completion(<span class="hljs-literal">nil</span>)
            }

            <span class="hljs-keyword">let</span> url = <span class="hljs-string">"\(AppConstants.API_URL)?u=\(deviceID)"</span>
            <span class="hljs-type">Alamofire</span>.request(url).validate().responseJSON { resp <span class="hljs-keyword">in</span>
                <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> data = resp.data, resp.result.isSuccess {
                    <span class="hljs-keyword">let</span> decoder = <span class="hljs-type">JSONDecoder</span>()
                    <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> settings = <span class="hljs-keyword">try</span>? decoder.decode(<span class="hljs-type">Settings</span>.<span class="hljs-keyword">self</span>, from: data) {
                        <span class="hljs-keyword">return</span> completion(settings)
                    }
                }

                completion(<span class="hljs-literal">nil</span>)
            }
        }

        <span class="hljs-keyword">fileprivate</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">updateRemoteSettings</span><span class="hljs-params">(<span class="hljs-number">_</span> settings: Settings, completion: @escaping<span class="hljs-params">(Bool)</span></span></span> -> <span class="hljs-type">Void</span>) {
            <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> deviceID = <span class="hljs-type">AppConstants</span>.deviceIDFormatted <span class="hljs-keyword">else</span> {
                <span class="hljs-keyword">return</span> completion(<span class="hljs-literal">false</span>)
            }

            <span class="hljs-keyword">let</span> params = settings.toParams()
            <span class="hljs-keyword">let</span> url = <span class="hljs-string">"\(AppConstants.API_URL)?u=\(deviceID)"</span>
            <span class="hljs-type">Alamofire</span>.request(url, method: .post, parameters: params).validate().responseJSON { resp <span class="hljs-keyword">in</span>
                <span class="hljs-keyword">guard</span> resp.result.isSuccess, <span class="hljs-keyword">let</span> res = resp.result.value <span class="hljs-keyword">as</span>? [<span class="hljs-type">String</span>: <span class="hljs-type">String</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 update settings."</span>, style: .danger).show()
                }

                completion((res[<span class="hljs-string">"status"</span>] == <span class="hljs-string">"success"</span>))
            }
        }

        <span class="hljs-keyword">fileprivate</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">updateCachedSettings</span><span class="hljs-params">(<span class="hljs-number">_</span> settings: Settings)</span></span> {
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> encodedSettings = <span class="hljs-keyword">try</span>? <span class="hljs-type">JSONEncoder</span>().encode(settings) {
                <span class="hljs-type">UserDefaults</span>.standard.<span class="hljs-keyword">set</span>(encodedSettings, forKey: <span class="hljs-type">SettingsService</span>.key)
            }
        }

        <span class="hljs-keyword">fileprivate</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">getCachedSettings</span><span class="hljs-params">()</span></span> -> <span class="hljs-type">Settings</span>? {
            <span class="hljs-keyword">let</span> defaults = <span class="hljs-type">UserDefaults</span>.standard
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> data = defaults.object(forKey: <span class="hljs-type">SettingsService</span>.key) <span class="hljs-keyword">as</span>? <span class="hljs-type">Data</span> {
                <span class="hljs-keyword">let</span> decoder = <span class="hljs-type">JSONDecoder</span>()
                <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> decodedSettings = <span class="hljs-keyword">try</span>? decoder.decode(<span class="hljs-type">Settings</span>.<span class="hljs-keyword">self</span>, from: data) {
                    <span class="hljs-keyword">return</span> decodedSettings
                }
            }

            <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
        }
    }
Enter fullscreen mode Exit fullscreen mode

Above we have the SettingsService. The first method loadSettings loads the settings from the API and then saves it locally. If there is no setting remotely, it calls the defaultSettings method and saves the response to the API.

The saveSettings method saves the Settings remotely using updateRemoteSettings and then locally using updateCachedSettings. The fetchRemoteSettings gets the settings from the API and decodes the response using the Swift decodable API.

Next, let’s define the Settings struct and have it extend Codable. In the same file for the SettingsService, add this above the SettingsService class definition:

    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Settings</span>: <span class="hljs-title">Codable</span> </span>{
        <span class="hljs-keyword">var</span> btc_min_notify: <span class="hljs-type">Int</span>?
        <span class="hljs-keyword">var</span> btc_max_notify: <span class="hljs-type">Int</span>?
        <span class="hljs-keyword">var</span> eth_min_notify: <span class="hljs-type">Int</span>?
        <span class="hljs-keyword">var</span> eth_max_notify: <span class="hljs-type">Int</span>?

        <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">toParams</span><span class="hljs-params">()</span></span> -> <span class="hljs-type">Parameters</span> {
            <span class="hljs-keyword">var</span> params: <span class="hljs-type">Parameters</span> = [:]

            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> btcMin = btc_min_notify { params[<span class="hljs-string">"btc_min_notify"</span>] = btcMin }
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> btcMax = btc_max_notify { params[<span class="hljs-string">"btc_max_notify"</span>] = btcMax }
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> ethMin = eth_min_notify { params[<span class="hljs-string">"eth_min_notify"</span>] = ethMin }
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> ethMax = eth_max_notify { params[<span class="hljs-string">"eth_max_notify"</span>] = ethMax }

            <span class="hljs-keyword">return</span> params
        }
    }
Enter fullscreen mode Exit fullscreen mode

Above we have a simple Settings struct that conforms to Codable. We also have a toParams method that converts the properties to a Parameters type so we can use it with Alamofire when making requests.

One last class we need to create is AppConstants. We will use this class to keep all the data that we expect to remain constant and unchanged throughout the lifetime of the application.

Create a AppConstants file and paste the following code:

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

    <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/api/settings"</span>
        <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> deviceID = <span class="hljs-type">UIDevice</span>.current.identifierForVendor?.uuidString
        <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> deviceIDFormatted = <span class="hljs-type">AppConstants</span>.deviceID?.replacingOccurrences(of: <span class="hljs-string">"-"</span>, with: <span class="hljs-string">"_"</span>).lowercased()
        <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_BEAMS_INSTANCE_ID"</span>
        <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> <span class="hljs-type">PUSHER_APP_KEY</span> = <span class="hljs-string">"PUSHER_APP_KEY"</span>
        <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> <span class="hljs-type">PUSHER_APP_CLUSTER</span> = <span class="hljs-string">"PUSHER_APP_CLUSTER"</span>
    }
Enter fullscreen mode Exit fullscreen mode

Replace the PUSHER_* keys with the values gotten from the Pusher Channels and Beams dashboard.

Updating the settings for the device

Now that we have defined the settings service, let’s update our controller so the user can set the minimum and maximum prices for each currency.

Open the CoinsTableViewController class and add the following method:

    <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> coin = coins[indexPath.row]

        <span class="hljs-keyword">var</span> minTextField: <span class="hljs-type">UITextField</span>?
        <span class="hljs-keyword">var</span> maxTextField: <span class="hljs-type">UITextField</span>?

        <span class="hljs-keyword">let</span> title = <span class="hljs-string">"Manage \(coin.name) alerts"</span>
        <span class="hljs-keyword">let</span> message = <span class="hljs-string">"Notification will be sent to you when price exceeds or goes below minimum and maximum price. Set to zero to turn off notification."</span>

        <span class="hljs-keyword">let</span> alert = <span class="hljs-type">UIAlertController</span>(title: title, message: message, preferredStyle: .alert)

        alert.addTextField { textfield <span class="hljs-keyword">in</span>
            minTextField = textfield
            textfield.placeholder = <span class="hljs-string">"Alert when price is below"</span>
        }

        alert.addTextField { textfield <span class="hljs-keyword">in</span>
            maxTextField = textfield
            textfield.placeholder = <span class="hljs-string">"Alert when price is above"</span>
        }

        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">guard</span> <span class="hljs-keyword">let</span> minPrice = minTextField?.text, <span class="hljs-keyword">let</span> maxPrice = maxTextField?.text <span class="hljs-keyword">else</span> {
                <span class="hljs-keyword">return</span> <span class="hljs-type">StatusBarNotificationBanner</span>(title: <span class="hljs-string">"Invalid min or max price"</span>, style: .danger).show()
            }

            <span class="hljs-keyword">var</span> btcMin: <span class="hljs-type">Int</span>?, btcMax: <span class="hljs-type">Int</span>?, ethMin: <span class="hljs-type">Int</span>?, ethMax: <span class="hljs-type">Int</span>?

            <span class="hljs-keyword">switch</span> coin.name {
            <span class="hljs-keyword">case</span> <span class="hljs-string">"BTC"</span>:
                btcMin = <span class="hljs-type">Int</span>(minPrice)
                btcMax = <span class="hljs-type">Int</span>(maxPrice)
            <span class="hljs-keyword">case</span> <span class="hljs-string">"ETH"</span>:
                ethMin = <span class="hljs-type">Int</span>(minPrice)
                ethMax = <span class="hljs-type">Int</span>(maxPrice)
            <span class="hljs-keyword">default</span>:
                <span class="hljs-keyword">return</span>
            }

            <span class="hljs-keyword">let</span> settings = <span class="hljs-type">Settings</span>(
                btc_min_notify: btcMin,
                btc_max_notify: btcMax,
                eth_min_notify: ethMin,
                eth_max_notify: ethMax
            )

            <span class="hljs-type">SettingsService</span>.shared.saveSettings(settings, completion: { saved <span class="hljs-keyword">in</span>
                <span class="hljs-keyword">if</span> saved {
                    <span class="hljs-type">StatusBarNotificationBanner</span>(title: <span class="hljs-string">"Saved successfully"</span>).show()
                }
            })
        }))

        present(alert, animated: <span class="hljs-literal">true</span>, completion: <span class="hljs-literal">nil</span>)
    }
Enter fullscreen mode Exit fullscreen mode

The method above is automatically called when a row is selected. In this method, we display a UIAlertController with two text fields for the minimum price and the maximum price. When the prices are submitted, the SettingsService we created earlier takes care of updating the values both locally and remotely.

Adding realtime cryptocurrency update support

Open the CoinsTableViewController and add the pusher property to the class as seen below:

<span class="hljs-keyword">var</span> pusher: <span class="hljs-type">Pusher</span>!Then replace the viewDidLoad method with the following code:

    <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()

        pusher = <span class="hljs-type">Pusher</span>(
            key: <span class="hljs-type">AppConstants</span>.<span class="hljs-type">PUSHER_APP_KEY</span>, 
            options: <span class="hljs-type">PusherClientOptions</span>(host: .cluster(<span class="hljs-type">AppConstants</span>.<span class="hljs-type">PUSHER_APP_CLUSTER</span>))
        )

        <span class="hljs-keyword">let</span> channel = pusher.subscribe(<span class="hljs-string">"currency-update"</span>)

        <span class="hljs-keyword">let</span> <span class="hljs-number">_</span> = channel.bind(eventName: <span class="hljs-string">"currency.updated"</span>) { data <span class="hljs-keyword">in</span>
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> data = data <span class="hljs-keyword">as</span>? [<span class="hljs-type">String</span>: [<span class="hljs-type">String</span>: [<span class="hljs-type">String</span>: <span class="hljs-type">Float</span>]]] {
                <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> payload = data[<span class="hljs-string">"payload"</span>] <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> }

                <span class="hljs-keyword">self</span>.coins = []

                <span class="hljs-keyword">for</span> (coin, deets) <span class="hljs-keyword">in</span> payload {
                    <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> currentPrice = deets[<span class="hljs-string">"current"</span>] <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> }
                    <span class="hljs-keyword">self</span>.coins.append(<span class="hljs-type">Coin</span>(name: coin, rate: currentPrice))
                }

                <span class="hljs-type">Dispatch</span>.main.async {
                    <span class="hljs-keyword">self</span>.tableView.reloadData()
                }
            }
        }

        pusher.connect()
    }
Enter fullscreen mode Exit fullscreen mode

In the code above, we are using the Pusher Swift SDK to subscribe to our currency-update Pusher Channel. We then subscribe to the currency.updated event on that channel. Whenever that event is triggered, we refresh the price of the cryptocurrency in realtime.

Adding push notifications to our iOS new application

To add push notification support, 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-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-type">PushNotifications</span>.shared.start(instanceId: <span class="hljs-type">AppConstants</span>.<span class="hljs-type">PUSHER_INSTANCE_ID</span>)
            <span class="hljs-type">PushNotifications</span>.shared.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-type">PushNotifications</span>.shared.registerDeviceToken(deviceToken) {
                <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> deviceID = <span class="hljs-type">AppConstants</span>.deviceIDFormatted {
                    <span class="hljs-keyword">try</span>? <span class="hljs-type">PushNotifications</span>.shared.subscribe(interest: <span class="hljs-string">"\(deviceID)_eth_changed"</span>)
                    <span class="hljs-keyword">try</span>? <span class="hljs-type">PushNotifications</span>.shared.subscribe(interest: <span class="hljs-string">"\(deviceID)_btc_changed"</span>)
                }
            }
        }
    }
Enter fullscreen mode Exit fullscreen mode

In the class above, we use the Pusher Beams Swift SDK to register the device for push notifications. We then subscribe to the *_eth_changed and *_btc_changed interests, where * is the device’s unique UUID.

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 by turning the switch ON.

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

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:

That’s all. 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. If you are using a physical device, you’ll need to expose your local API using Ngrok and then change the API_URL InAppConstants.

Anytime you want to update the currency prices, run the command below manually in your Laravel application:

    $ php artisan schedule:run

Enter fullscreen mode Exit fullscreen mode

Here is a screen recording of the application in action:

Conclusion

In this article, we have been able to see how easy it is to create a cryptocurrency alert website using Laravel, Swift, Pusher Channels and Pusher Beams. The source code to the application built in this article is available on GitHub.

This post first appeared on the Pusher blog.

Top comments (0)