Nowadays, gathering data is one of the keys to understanding how products are perceived. Gathering some data from users can help you build better products and understand your users. However, all the data in the world would be useless without a way to visualize it.
In this article, we will explore how to create a simple realtime chart in iOS. The chart will receive data and update in realtime to the screens of everyone currently logged into your application. We will assume this is a chart that monitors how many visitors are using a website. Let’s begin.
For context, here is a sample of what we will be building:
Before we begin this tutorial, you will need to have the following requirements settled:
When you have all the requirements, then we can begin.
Launch Xcode on your Mac and create a new project (call it whatever you want). Follow the new application wizard and create a new Single-page application. Once the project has been created, close Xcode and launch your terminal application.
In the terminal, cd
to the root of the application directory. Then run the command pod init
. This will generate a Podfile. Update the contents of the Podfile to the contents below (replace PROJECT_NAME
with your project name):
1platform :ios, '9.0' 2 target 'PROJECT_NAME' do 3 use_frameworks! 4 pod 'Charts', '~> 3.0.2' 5 pod 'PusherSwift', '~> 4.1.0' 6 pod 'Alamofire', '~> 4.4.0' 7 end
Save the Podfile and then go to your terminal and run the command: pod install
.
Running this command will install all the third-party packages we need to build our realtime iOS chart application.
The first package it will install is Charts, which is a package for making beautiful charts on iOS. The second package is the Pusher swift SDK. The last package is Alamofire, a package for making HTTP requests on iOS.
Once the installation is complete, open the **.xcworkspace**
file in your project directory root. This should launch Xcode. Now we are ready to start creating our iOS application.
To begin, we will create the necessary views we need for our realtime chart application. Open the Main.storyboard file and let’s start designing our view.
First, create a rectangular view from edge to edge at the top of the View Controller in the storyboard. In that view, add a button and add the title “Simulate Visits”. Next, create another view that is also a rectangle, spanning from the end of the first view above to the bottom of the screen. This view will be where we will render the realtime chart.
When you are done creating the views, you should have something like shown in the image below.
As it currently stands, the views do nothing. Let us connect some functionality to the iOS chart application view.
As said before, our application’s views and buttons are not connected to our ViewController
so let’s fix that.
In Xcode, while the storyboard is still open, click on the “Show the Assistant Editor” button on the top right of the page to split the view into storyboard and code view. Now, click once on the button you created, and while holding ctrl
, click and drag the link to the code editor. Then create an @IBaction
as seen in the images below:
When the link is complete, you should see something like this added to the code editor:
1@IBAction func simulateButtonPressed(_ sender: Any) { 2 }
Great! Now that you have created the first link, we will have to create one more link to the chart view.
On your storyboard, click the view and on the “Identity Inspection” tab, make sure the view is connected to LineChartView
as seen below.
Now that the view is connected to a view class, repeat the same as we did before to link the button, only this time instead of creating an @IBAction
we will create an @IBOutlet
. Images are shown below:
When the link is complete, you should see something like this added to the code editor:
@IBOutlet weak var chartView: LineChartView!
Finally, at the top of the ViewController
import the Charts package. You can add the code below right under import UIKit
in the ViewController
.
import Charts
Now that we have linked both elements to our code, every time the Simulate Visits button is pressed, the simulateButtonPressed function will be called.
The final piece of the puzzle will be displaying a chart and making it update in realtime across all devices viewing the chart.
To achieve this, we will do the following:
Let’s create the function that updates our chart depending on the numbers supplied to it. Open the ViewController
, and in it declare a class property right under the class declaration. We will use this property to track the visitors:
var visitors: [Double] = []
Next, we will add the function that will do the actual update to the chart view:
1private func updateChart() { 2 var chartEntry = [ChartDataEntry]() 3 4 for i in 0..<visitors.count { 5 let value = ChartDataEntry(x: Double(i), y: visitors[i]) 6 chartEntry.append(value) 7 } 8 9 let line = LineChartDataSet(values: chartEntry, label: "Visitor") 10 line.colors = [UIColor.green] 11 12 let data = LineChartData() 13 data.addDataSet(line) 14 15 chartView.data = data 16 chartView.chartDescription?.text = "Visitors Count" 17 }
In the code above, we declare chartEntry
where we intend to store all our chart data. Then we loop through the available visitors
and, for each of them, we add a new ChartDataEntry(x: Double(i), y: visitors[i])
that tells the chart the X and Y positions.
We set the color the line chart will be displayed in. We create the LineChartData
and add the line
which contains our data points. Finally, we add the data to the chartView
and set the chart view description.
The next thing we need to do is make our request button trigger a backend which will in turn send simulated data to Pusher.
To do this, we need to update the view controller one more time. In the ViewController
import the Alamofire package right under the Charts package:
import Alamofire
Now replace the simulateButtonPressed
function with the code below:
1@IBAction func simulateButtonPressed(_ sender: Any) { 2 Alamofire.request("http://localhost:4000/simulate", method: .post).validate().responseJSON { (response) in 3 switch response.result { 4 case .success(_): 5 _ = "Successful" 6 case .failure(let error): 7 print(error) 8 } 9 } 10 }
In the code below, we use Alamofire to send a POST request to http://localhost:4000/simulate which is a local web server (we will create this backend soon). In a real application, this will typically point to a real web server.
This endpoint does not take any parameters in order to keep the tutorial simple. We also do not need to do anything with the response. We just need the POST request to be sent every time the simulate visits button is pressed.
To make all this work, we will create a function that listens for events from Pusher and, when one is received, we save the value to visitors
and then trigger the update chart function we created earlier.
To do this, open the ViewController
and import the PusherSwift
SDK under the Alamofire package at the top:
import PusherSwift
Next, we will declare a class property for the Pusher instance. We can do this right under the visitors
declaration line:
var pusher: Pusher!
Then after declaring the property, we need to add the function below to the class so it can listen to the events:
1private func listenForChartUpdates() { 2 pusher = Pusher(key: "PUSHER_KEY", options: PusherClientOptions(host: .cluster("PUSHER_CLUSTER"))) 3 4 let channel = pusher.subscribe("visitorsCount") 5 6 channel.bind(eventName: "addNumber", callback: { (data: Any?) -> Void in 7 if let data = data as? [String: AnyObject] { 8 let count = data["count"] as! Double 9 self.visitors.append(count) 10 self.updateChart() 11 } 12 }) 13 14 pusher.connect() 15 }
In the code above, we instantiate Pusher and pass in our key and the cluster (you can get your key and cluster from your Pusher application’s dashboard). We then subscribe to the visitorsChannel
and bind to the event name addNumber
on that channel.
When the event is triggered, we fire the logic in the callback which simply appends the count to visitors
and then calls the updateChart
function, which updates the actual Chart in realtime.
Finally we call pusher.connect()
which forms the connection to Pusher.
In the viewDidLoad
function just add a call to the listenForChartUpdates
method:
1override func viewDidLoad() { 2 super.viewDidLoad() 3 4 // ...stuff 5 6 listenForChartUpdates() 7 }
That’s all! We have created our application in Xcode and we are ready for testing. However, to test, we need to create the backend that we send a POST
request to when the button is clicked. To create this backend, we will be using Node.js. Let’s do that now.
To get started, create a directory for the web application and then create some new files inside the directory:
File: index.js
1// ------------------------------------------------------- 2 // Require Node dependencies 3 // ------------------------------------------------------- 4 5 let Pusher = require('pusher'); 6 let express = require('express'); 7 let bodyParser = require('body-parser'); 8 let app = express(); 9 10 // Instantiate Pusher 11 let pusher = new Pusher(require('./config.js')); 12 13 // ------------------------------------------------------- 14 // Load express middlewares 15 // ------------------------------------------------------- 16 17 app.use(bodyParser.json()); 18 app.use(bodyParser.urlencoded({ extended: false })); 19 20 // ------------------------------------------------------- 21 // Simulate multiple changes to the visitor count value, 22 // this way the chart will always update with different 23 // values. 24 // ------------------------------------------------------- 25 26 app.post('/simulate', (req, res, next) => { 27 var loopCount = 0; 28 let sendToPusher = setInterval(function(){ 29 let count = Math.floor((Math.random() * (100 - 1)) + 1) 30 pusher.trigger('visitorsCount', 'addNumber', {count:count}) 31 loopCount++; 32 if (loopCount === 20) { 33 clearInterval(sendToPusher); 34 } 35 }, 2000); 36 res.json({success: 200}) 37 }) 38 39 40 // Handle index 41 app.get('/', (req, res) => { 42 res.json("It works!"); 43 }); 44 45 // Handle 404's 46 app.use((req, res, next) => { 47 let err = new Error('Not Found'); 48 err.status = 404; 49 next(err); 50 }); 51 52 // ------------------------------------------------------- 53 // Serve application 54 // ------------------------------------------------------- 55 56 app.listen(4000, function(){ 57 console.log('App listening on port 4000!') 58 });
The file above is a simple Express application written in JavaScript. We instantiate all the packages we require and configure pusher using a config file we will create soon. Then we create a route /simulate
and in this route we trigger the addNumber
event in the visitorCount
channel. This is the same channel and event the application is listening for.
To make it a little easier, we use setInterval
to send a random visitor count to the Pusher backend every 2000 milliseconds. After looping for 20 times, the loop stops. This should be sufficient to test our application.
Create the next file config.js:
1module.exports = { 2 appId: 'PUSHER_APP_ID', 3 key: 'PUSHER_APP_KEY', 4 secret: 'PUSHER_APP_SECRET', 5 cluster: 'PUSHER_APP_CLUSTER', 6 };
Replace the PUSHER_APP_*
keys with the credentials from your own Pusher application.
The next and final file is package.json:
1{ 2 "main": "index.js", 3 "dependencies": { 4 "body-parser": "^1.16.0", 5 "express": "^4.14.1", 6 "pusher": "^1.5.1" 7 } 8 }
In this file we simply declare dependencies.
Now open terminal and cd
to the root of the web application directory and run the commands below to install the NPM dependencies and run the application respectively:
1$ npm install 2 $ node index.js
When installation is complete and the application is ready you should see the output below:
Once you have your local node web server running, you will need to make some changes so your application can talk to the local web server. In the info.plist
file, make the following changes:
With this change, you can build and run your application and it will talk directly with your local web application.
This article has shown you how you can combine Pusher and the Charts package to create a realtime iOS chart application. There are many other chart types you can create using the package but, for brevity, we have done the easiest. You can explore the other chart types and even pass in multiple data points per request.