Swift: Getting the Levels of Water in São Paulo

I am brazilian and we are facing a water crisis (specially in São Paulo state), the drought is the most severe in the history of São Paulo and the people become interested in knowing the levels in the Cantareira reservoir system.

The state water authority, SABESP, has a website that shows the current levels with daily updates.

An API (https://github.com/rafaell-lycan/sabesp-mananciais-api) was made based on the data available on this website and we can follow the level changes of each reservoir in the Cantareira reservoir system.

As usual, the source code of this post is available at my github.

API Details

My friend, William Bruno (https://github.com/wbruno), is a contributor of this API.

The API address is https://sabesp-api.herokuapp.com/.

Besides the name of the reservoir, it has the current level (volume armazenado), rainfall of the day (pluviometria do dia), accumulated rainfall of the month (pluviometria acumulada do mês) and historical average of the month (média histórica do mês).

The API returns the following JSON:

[
    {
        "name": "Cantareira",
        "data": [
            {
                "key": "volume armazenado",
                "value": "18,9 %"
            },
            {
                "key": "pluviometria do dia",
                "value": "16,4 mm"
            },
            {
                "key": "pluviometria acumulada no mês",
                "value": "206,3 mm"
            },
            {
                "key": "média histórica do mês",
                "value": "178,0 mm"
            }
        ]
    },
    {
        "name": "Alto Tietê",
        "data": [
            {
                "key": "volume armazenado",
                "value": "22,8 %"
            },
            {
                "key": "pluviometria do dia",
                "value": "7,3 mm"
            },
            {
                "key": "pluviometria acumulada no mês",
                "value": "186,4 mm"
            },
            {
                "key": "média histórica do mês",
                "value": "172,4 mm"
            }
        ]
    },
    {
        "name": "Guarapiranga",
        "data": [
            {
                "key": "volume armazenado",
                "value": "85,0 %"
            },
            {
                "key": "pluviometria do dia",
                "value": "11,2 mm"
            },
            {
                "key": "pluviometria acumulada no mês",
                "value": "200,6 mm"
            },
            {
                "key": "média histórica do mês",
                "value": "153,2 mm"
            }
        ]
    },
    {
        "name": "Alto Cotia",
        "data": [
            {
                "key": "volume armazenado",
                "value": "64,6 %"
            },
            {
                "key": "pluviometria do dia",
                "value": "1,0 mm"
            },
            {
                "key": "pluviometria acumulada no mês",
                "value": "150,4 mm"
            },
            {
                "key": "média histórica do mês",
                "value": "149,1 mm"
            }
        ]
    },
    {
        "name": "Rio Grande",
        "data": [
            {
                "key": "volume armazenado",
                "value": "97,3 %"
            },
            {
                "key": "pluviometria do dia",
                "value": "2,6 mm"
            },
            {
                "key": "pluviometria acumulada no mês",
                "value": "199,4 mm"
            },
            {
                "key": "média histórica do mês",
                "value": "186,3 mm"
            }
        ]
    },
    {
        "name": "Rio Claro",
        "data": [
            {
                "key": "volume armazenado",
                "value": "43,9 %"
            },
            {
                "key": "pluviometria do dia",
                "value": "4,0 mm"
            },
            {
                "key": "pluviometria acumulada no mês",
                "value": "235,2 mm"
            },
            {
                "key": "média histórica do mês",
                "value": "245,9 mm"
            }
        ]
    }
]

The returned data showed above are from the current day; if we want to get data of past days, we have to send the desired date at the URL of the API, i.e. https://sabesp-api.herokuapp.com/2015-01-01.

Creating the iOS Project

It is not my goal teach how to develop in iOS, though you can get the project at https://github.com/rpresb/ios-mananciais-sabesp and if you have any issue leave your comment below.

Below are the screenshots with the options I chose to create the project.

First of all, choose the project template Single View Application and click Next

Captura de Tela 2015-03-30 às 22.35.13

In the next window, fill the fields Product Name, Organization Name, Organization Identifier, Language and Devices.

Captura de Tela 2015-03-30 às 22.35.04

Remember to uncheck the Use Core Data checkbox and select the Swift language.

Coding

Although is not my intention in this post to teach you how to develop in Swift, I will try to explain the following code that I made for this app.

Feel free to download the code at https://github.com/rpresb/ios-mananciais-sabesp.

The file Manancial.swift

import Foundation

struct Manancial {
    var name:String
    var volume:String
    var rainDay:String
    var rainMonth:String
    var rainAvg:String
    
    init(manancialDic:NSDictionary) {
        NSLog("%@", manancialDic);
        
        name = manancialDic["name"] as String
        
        var data = manancialDic["data"] as NSArray
        volume = data[0]["value"] as String
        rainDay = data[1]["value"] as String
        rainMonth = data[2]["value"] as String
        rainAvg = data[3]["value"] as String
    }
    
}

In this file we have the structure that will be used to manipulate the data that we get from the API.

In the constructor of the Manancial class, we receive a NSDictionary with the JSON returned from the API.

For each reservoir (manancial), we have an array named data which has 4 elements.

Considering that the elements are always returned in the same order, we can use the hard-coded indexes to set the data volume, rainDay, rainMonth e rainAvg.

File ViewController.swift

import UIKit

class ViewController: UIViewController {
    
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var volumeLabel: UILabel!
    @IBOutlet weak var dayLabel: UILabel!
    @IBOutlet weak var monthLabel: UILabel!
    @IBOutlet weak var avgLabel: UILabel!
    @IBOutlet weak var page:UIPageControl!
    @IBOutlet weak var level:UIView!
    
    var mananciais: NSArray!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        nameLabel.text = "Carregando..."
        volumeLabel.hidden = true
        dayLabel.hidden = true
        monthLabel.hidden = true
        avgLabel.hidden = true
        level.hidden = true

        loadData();
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    func loadData() {
        let baseURL = NSURL(string: "https://sabesp-api.herokuapp.com/")
        let sharedSession = NSURLSession.sharedSession()
        let downloadTask: NSURLSessionDownloadTask = sharedSession.downloadTaskWithURL(baseURL!, completionHandler: { (location: NSURL!, response:NSURLResponse!, error: NSError!) -> Void in
            if (error == nil) {
                let dataObject = NSData(contentsOfURL: location)
                self.mananciais = NSJSONSerialization.JSONObjectWithData(dataObject!, options: nil, error: nil) as NSArray
                
                dispatch_async(dispatch_get_main_queue(), { () -> Void in
                    self.page.numberOfPages = self.mananciais.count
                    self.showPage(0)
                })
            } else {
                NSLog("%@", error)
                let networkIssueController = UIAlertController(title: "Error", message: "Unable to load data. Connectivity error!", preferredStyle: .Alert)
                let okButton = UIAlertAction(title: "OK", style: .Default, handler: nil)
                networkIssueController.addAction(okButton)
                self.presentViewController(networkIssueController, animated: true, completion: nil)
                
                dispatch_async(dispatch_get_main_queue(), { () -> Void in
                    self.nameLabel.text = "Erro"
                    self.volumeLabel.hidden = true
                    self.dayLabel.hidden = true
                    self.monthLabel.hidden = true
                    self.avgLabel.hidden = true
                    self.level.hidden = true
                })
            }
        })
        
        downloadTask.resume()
    }
    
    func showPage(index:Int) {
        let manancial = Manancial(manancialDic: self.mananciais[index] as NSDictionary)
        
        self.nameLabel.text = "\(manancial.name)"
        self.volumeLabel.text = "\(manancial.volume)"
        self.dayLabel.text = "\(manancial.rainDay)"
        self.monthLabel.text = "\(manancial.rainMonth)"
        self.avgLabel.text = "\(manancial.rainAvg)"
        
        self.volumeLabel.hidden = false
        self.dayLabel.hidden = false
        self.monthLabel.hidden = false
        self.avgLabel.hidden = false
        self.level.hidden = false
        
        var volume:NSDecimalNumber = NSDecimalNumber(string: manancial.volume.stringByReplacingOccurrencesOfString(" %", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil).stringByReplacingOccurrencesOfString(",", withString: ".", options: NSStringCompareOptions.LiteralSearch, range: nil))
        
        var pixel = CGFloat(volume) / 100 * self.view.frame.size.height

        let y:CGFloat = self.view.frame.size.height - pixel;
        
        self.level.frame.origin.y = y
        self.level.frame.size.height = pixel
    }

    @IBAction func pageChanged() {
        showPage(page.currentPage);
    }

}

In the function viewDidLoad we call the function loadData which is responsible to request the data in the API.

After get the JSON with the data, we serialize it into a NSArray object.

This object is our data source to show the reservoir information and create a page for each reservoir.

Each element in this array is a NSDictionary which we will send to the function showPage when the user changes the page.

The next step is to get the first page; we have to send the first element of the array to the function showPage.

The running app is shown below

Captura de Tela 2015-03-31 às 21.27.16

 

Conclusion

We have worked with another interesting API.

I chose iOS with Swift to work with this API to show that, even with a very simple API we can make a creative usage to offer an interesting experience to the user.

Although is not my goal to teach how to develop in Swift, this could be a starting point to you understand how it works and get excited to go deeply in the iOS development.

If you have any question, issue or suggestion, leave your comment below that I will answer with pleasure.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

Up ↑

%d bloggers like this: