Make App Pie

Training for Developers and Artists

How to Read XML Files From the Web in Swift

You’ve probably heard of XML. You definitely have used an XML file If one data format is considered ubiquitous and prevalent in modern computing, it’s probably XML. XML or eXtensible Markup Language is a highly flexible language for an amazing variety of data storage solutions. Microsoft Office uses it for spreadsheets and documents. Podcasts and blog posts use the XML based RSS feeds for distribution. Virtually all e-book formats are XML or contain XML. Xcode uses XML for storyboard files and property lists.

You could even build data bases in XML. There is one option for Core Data to use XML. Even if you don’t go as far as Core Data, Developers  use XML to in NSUserDefaults and plists for applications. Much of the external data we can add to an application also comes in an XML format. Understanding how to use this format is a critical skill for good application development.

A lot of data you can read externally from the web will be XML data. In this lesson we’ll introduce you to XML, and show you how to load data from the Web,  read an XML file quickly and easily using the NSXMLParser and its delegate.

We’re going to build a colorful app with a tableview for a demo. If you know about XML and  want get to code for parsing XML and are not interested in building the full demo, you can skip down to my code by clicking here

What is XML?

XML is a text file with special opening and closing tags. Most of these tags look like HTML, however there’s a difference: there are no reserved tags. The author makes up whatever tags they want. For example this is properly formed XML




<menu>
    <item>
        <name>Cheese Pizza</name>
        <size>Small</size>
        <price>9.99</price>
    </item>
    <item>
        <name>Cheese Pizza</name>
        <size>Large</size>
       <price>19.99</price>
    </item>
    <item>
       <name>Pepperoni Pizza</name>
       <size>Small</size>
       <price>11.99</price>
    </item>
</menu>



That makes up this table.

Cheese Pizza Small 9.99
Cheese Pizza Large 19.99
Pepperoni Pizza Small 11.99

The XML file author sets the tags menu,item,name,size and price. Their meaning is the meaning the author assigns them. Each element is enclosed with a starting tag such as and and ending tag with a slash such as <menu>  and </menu>

That’s the basics of XML. Where it gets interesting tags  have other tags nested within them. I could include all the options for size in one item, rewriting the above as






<menu>
    <item>
        <name>Cheese Pizza</name>
        <size>
            <sizeOption>Small</sizeOption>
            <sizeOption>Large</sizeOption>
       </size>
       <price>
         <priceOption>9.99</priceOption>
         <priceOption>19.99</priceOption>
       </price>
    </item>
    <item>
        <name>Pepperoni Pizza</name>
        <size>
              <sizeOption>Small</sizeOption>
              <sizeOption>Large</sizeOption>
        </size>
        <price>
             <priceOption>11.99</priceOption>
             <priceOption>22.99</priceOption>
        </price>
    </item>
</menu>





I’ve grouped all cheese pizzas together with options for large and small with corresponding prices in one item.

Many open data portals and commercial web sites can transmit data in XML. For example, ther is the Fremont Bridge Bicycle Count Data. We’ve used this data in two previous lessons on Introducing open data portals and Importing web data with CSV. We can also export a XML file from the data portal as well. Three rows from that data would export like this

<response><row _id="row-heaa.bmup_u9ri" _uuid="00000000-0000-0000-5F10-FAFF51505057" _position="0" _address="http://data.seattle.gov/resource/_4xy5-26gy/row-heaa.bmup_u9ri"><date>2016-04-01T06:00:00</date><fremont_bridge_nb>94</fremont_bridge_nb><fremont_bridge_sb>43</fremont_bridge_sb></row><row _id="row-4bph-fbz7~z8ak" _uuid="00000000-0000-0000-B620-CFF016DB113B" _position="0" _address="http://data.seattle.gov/resource/_4xy5-26gy/row-4bph-fbz7~z8ak"><date>2016-04-01T07:00:00</date><fremont_bridge_nb>256</fremont_bridge_nb><fremont_bridge_sb>106</fremont_bridge_sb></row><row _id="row-m682~bpga_j7rd" _uuid="00000000-0000-0000-94E3-23CA2639451F" _position="0" _address="http://data.seattle.gov/resource/_4xy5-26gy/row-m682~bpga_j7rd"><date>2016-04-01T08:00:00</date><fremont_bridge_nb>346</fremont_bridge_nb><fremont_bridge_sb>153</fremont_bridge_sb></row></response>

Since machines don’t care about white spaces, the downloaded data has none. We can clean it up to read it. Before we completely clean it up, notice the row tag has a lot of extra stuff on it like this

<row _id="row-heaa.bmup_u9ri" _uuid="00000000-0000-0000-5F10-FAFF51505057" _position="0" _address="http://data.seattle.gov/resource/_4xy5-26gy/row-heaa.bmup_u9ri">

These items like _id, _uuid and so on are attributes of the tag. For some data sets they become important ways of organizing and including extra information about our data. We’ll discuss using attributes in a later lesson.  In this lesson, we’ll ignore them for simplicity’s sake. If we remove the attributes and indent everything, we get an XML file like this:

<response>
    <row>
        <date>2016-04-01T06:00:00</date>
       <fremont_bridge_nb>94</fremont_bridge_nb>
       <fremont_bridge_sb>43</fremont_bridge_sb>
    </row>
    <row>
         <date>2016-04-01T07:00:00</date>
         <fremont_bridge_nb>256</fremont_bridge_nb>
         <fremont_bridge_sb>106</fremont_bridge_sb>
    </row>
    <row>
        <date>2016-04-01T08:00:00</date>
        <fremont_bridge_nb>346</fremont_bridge_nb>
        <fremont_bridge_sb>153</fremont_bridge_sb>
    </row>
</response>

The three tags  date, fremont_bridge_nb, and fremont_bridge_sb mark fields found within one row.
Let’s use Fremont Bridge Bicycle traffic data from April 2016 to make a list of hourly bicycle counts. We’ll  take XML bike data specified from a URL, convert it into a model, and then preset that data in a table view, changing the color of the cell’s background depending on traffic volume and direction.

Set up the Table View

In Xcode make a new single view project called FremontBicycleXMLDemo. Use Swift as the language, with a universal device. Go to the ViewController.swift and change the class declaration from this

class ViewController: UIViewController {

to this.

class ViewController: UITableViewController, NSXMLParserDelegate {

Instead of using the table view template, we’ll manually add a table view using this class. We changed UIViewController to UITableViewController for that reason. We also added a delegate NSXMLParserDelegate which we will use to parse the XML file.

Go to the storyboard. Open the document outline if not already open by clicking the document outline icon icon on the lower left of the storyboard. Select the View Controller Scene on the outline.

2016-06-01_06-24-30

Press Delete on the keyboard to remove it. Press Command-Shift-K to force a cleaning of the project, removing anything that might mess up our storyboard work. Find the Table View Controller in the object library and drag to the storyboard. In the attributes inspector, find the View Controller section and click on Is Initial View Controller

2016-06-01_06-26-37

In the Identity inspector, change the Class to View Controller

2016-06-01_06-30-01

In the document outline, open up the View Controller Scene until you can select the Table View Cell

2016-06-01_06-31-47

In the properties inspector, change the Style to Subtitle and the Identifier to Cell

2016-06-01_06-35-33

We’ve set up the storyboard for a table view.  Everything else will be code.

2016-06-01_06-38-41

The Model for the Project

In the CSV lesson we used a dictionary to store the data from an external file. This time we’ll use two classes. While dictionaries can be flexible and much easier to add data automatically from the file to the model,  dictionaries of the form [String:AnyType] can sometimes be annoying keeping track of everything’s type. A class or a struct is strongly typed, so doesn’t have that problem. A class requires a bit more tedious work to set up each type correctly and assign correctly.  We’ll use two classes for this model. One will be a class for the row,  the second for the collection of rows.

Press Command-N and make a new Cocoa Touch Class named  BikeModel subclassing NSObject. Change the classes to this:

class BikeRow:NSObject {
    var date:NSDate! = NSDate()
    var northBound:Int! = nil
    var southBound:Int! = nil
}


class BikeModel: NSObject {
    var data:[BikeRow] = []
}

BikeRow contains all optional properties. Later in the lesson we’ll see why.

That’s all we need for a model. To make adding data a bit easier,  add an initializer to BikeRow

class BikeRow:NSObject {
    var date:NSDate! = NSDate()
    var northBound:Int! = nil
    var southBound:Int! = nil
    override init(){}
    init(date:NSDate,northBound:Int,southBound:Int){
        self.date = date
        self.northBound = northBound
        self.southBound = southBound
    }
}

Later in our code, we’ll use the maximum  monthly traffic volume for both directions of traffic for coloring the table view. Change the BikeModel to this:

class BikeModel: NSObject {
    var data:[BikeRow] = []
    var maxSouthBound = 0
    var maxNorthBound = 0
    func addRow(row:BikeRow){
        data += [row]
        if row.northBound > maxNorthBound{
            maxNorthBound = row.northBound
        }
        if row.southBound > maxSouthBound{
            maxSouthBound = row.southBound
        }

    }
}

Complete the Table for Testing

With the model defined, we have enough to add the  three required data sources for the table view. Add this to the ViewController class

var bikeModel = BikeModel()

 //MARK: Table View Delegates and Data sources
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return bikeModel.data.count
    }

If you are not familiar with table view data sources, either check out this post or buy my book Swift Swift View Controllers to learn more. Add the last data source to configure the cells:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
    let row = indexPath.row
    let nb = bikeModel.data[row].northBound
    let sb = bikeModel.data[row].southBound
    let date = bikeModel.data[row].date
    let backgroundColor = backgroundColorForRow(row)
    cell.backgroundColor = backgroundColor
    //Bridge data
    let counts = String(format:"NorthBound: %i SouthBound: %i",nb,sb)
    cell.textLabel?.text = counts
    //Date
    let dateFormatter = NSDateFormatter()
     dateFormatter.dateStyle = .FullStyle
     dateFormatter.timeStyle = .ShortStyle
     let dateString = dateFormatter.stringFromDate(date)
     cell.detailTextLabel?.text = dateString
     return cell
}

We’re putting the counts for both northbound and southbound in  textLabel, and the date in  detailTextLabel. Note we used the .Fullstyle date format so we would have a day of the week. We also called a new method backgroundColorForRow which we need to define.  Add this:

 func backgroundColorForRow(row:Int)-> UIColor{
    let nb = bikeModel.data[row].northBound
    let sb = bikeModel.data[row].southBound
    if nb > sb {
       let tint =  1.0 - CGFloat(nb) / CGFloat(bikeModel.maxNorthBound)
       return UIColor(red: tint, green: tint, blue: 1.0, alpha: 1.0)
    } else {
       let tint = 1.0 - CGFloat(sb) / CGFloat(bikeModel.maxSouthBound)
       return UIColor(red: 1.0,green:tint,blue: tint,alpha: 1.0)
    }
}

This method returns a shade of blue if there are more northbound bikes and red if there are more southbound bikes. The shade goes from white to faded to full color depending on the volume of traffic. I’ve defined the shade based on the ratio of current traffic to the maximum traffic for the month in that direction. To make the highest color the solid color, I subtracted the ratio from one, then used an RGB trick of varying the other two colors by the same amount.

Add some test data to viewDidLoad:

//MARK: - Life cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        //test data
        bikeModel.addRow(BikeRow(date: NSDate(),northBound: 12, southBound: 24))
        bikeModel.addRow(BikeRow(date: NSDate(),northBound: 36, southBound: 14))
    }

Build and run. You get two cells in your table. (background darkened for contrast)

2016-06-01_07-43-15

How to Read and Parse an XML File

All XML elements have three parts: a Start tag, content, and a End tag. XML tags can be nested. The context of one element’s content will be different from another element. For example we have this row:

<row>
        <date>2016-04-01T06:00:00</date>
       <fremont_bridge_nb>94</fremont_bridge_nb>
       <fremont_bridge_sb>43</fremont_bridge_sb>
    </row>

There are four elements. The date,fremont_bridge_nb, and fremont_bridge_sb contain data for content. The row tag contains date,fremont_bridge_nb, and fremont_bridge_sb. We do different things for each tag, and at different parts of that tag.

This is what the NSXMLParser‘s delegate does. It gives us methods where we code the activity for each part of the element. We read a file in with NSXMLParser, and the delegates do the rest of the work. with beginning tags, we’ll do our initialization. In ending tags, we’ll turn content into values for our model. In between, we’ll collect content, if any.

Read The  XML File from a URL

Our first task is reading the XML file. Add this code to ViewController

      //MARK: - XML Parsing Code
    func beginParsing(urlString:String){
        guard let myURL = NSURL(string:urlString) else {
            print("URL not defined properly")
            return
        }
        guard let parser = NSXMLParser(contentsOfURL: myURL) else {
            print("Cannot Read Data")
            return
        }
        parser.delegate = self
        bikeModel = BikeModel()
        if !parser.parse(){
            print("Data Errors Exist:")
            let error = parser.parserError!
            print("Error Description:\(error.localizedDescription)")
            print("Error reason:\(error.localizedFailureReason)")
            print("Line number: \(parser.lineNumber)")
        }
        tableView.reloadData()

This function’s parameter is a string representing a URL. We change that to a NSURL, using guard to make sure we have a well formed URL. If not, we leave the method, sending a message there’s a problem. Then we use NSXMLParser initializer to load the XML into the parser. The parser can read from a URL, as we do here. NSXMLParser can also read from a NSData object, or from streaming data. NSXMLParser uses a delegate to interpret the read data, and our next statement tells us we will code the delegate methods in this class. When we are sure we have valid data read,  we clear our current model then parse, adding new rows and fields to the clean model. This way, if anything goes wrong, we still have our old data displaying, instead of a blank table.  The parse method returns a Bool, which is false if we encounter an error. I set this to print the error to the console. As our last step, we update our table to show the parsed data.

There’s something here which you might consider a bug. In the case of an error in parsing, our model is corrupt or incomplete. Do we roll back to the model before we parsed? Do we clear all data? I went with a third option and just left the corrupt database. For our purposes, seeing what did or did not work will help in debugging. I would not do that in a real app though. I’d pick one of the other two, depending on program and user requirements, and post an alert that the file was corrupt.

The First Delegate Method: The Beginning Tag

The first of the delegate methods parser:didStartElement: begins a tag. Before we add the method, we need two more properties. Add these two properties to the top of the ViewController.

var bikeRow = BikeRow()
var currentContent = String()

Add this below the beginParsing method:

   //MARK: NSXMLParser delegates
    func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]){
        print("Beginning tag: <\(elementName)>")
        if elementName == "row"{
            bikeRow = BikeRow()
        }
        currentContent = ""
    }

In start tags, we initialize. I could initialize every item. I went simple and only initialized bikeRow when we start a row and currentContent for every start. For debugging purposes I put a print statement in the method, as I did throughout this code. I used the parameter elementName to know when to initialize bikeRow when I have a <row> tag.

The Second Delegate Method: The Content

Sandwiched between the beginning and end tag is the content. We have a delegate method parser:foundCharacters with a parameter string containing the content as a string.  Add this code under the first delegate method

//the middle of an element
    //append the string for the element
    func parser(parser: NSXMLParser, foundCharacters string: String){
        currentContent += string
        print("Added to make \(currentContent)")
    }

You might be wondering why we used currentContent += string and not currentContent = string. parser:foundCharacters may return parts of the content. To make sure we get all of it we append string to currentContent instead of assigning it.

The Third Delegate Method: The End

The detection of an end tag  such as </row> or </date> calls the parser:didEndElement: delegate method. This is where we make this into internal data. In this method is code to to convert currentContent to the correct type, and store it in the correct place. Add this below our other two delegate methods

func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?){
        print("ending tag: </\(elementName)> with contents:\(currentContent)")
        switch elementName{
        case "row":
            bikeModel.addRow(bikeRow)
            print ("model has \(bikeRow)")
        case"fremont_bridge_nb":
            bikeRow.northBound = Int(currentContent)
        case "fremont_bridge_sb":
            bikeRow.southBound = Int(currentContent)
        case "date":
            let dateFormatter = NSDateFormatter()
            dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
            bikeRow.date = dateFormatter.dateFromString(currentContent)
        default:
            return
        }
    }

A switch statement parses the tags of interest.  If not something interesting such as the  </response> tag,  I ignore it by returning from the function in the default of the switch. For fremont_bridge_nb and fremont_bridge_sb tags , the code converts the currentContent string to an optional Int. This is why we set the BikeData properties to optionals. It’s anther error check. Int returns nil if  currentContent cannot be cast as an integer.

The date tag is slightly more complicated. We use the NSDateFormatter method dateFromString. This will require us to set a specific date format set as a string in the dateFormat property. Dates have many formats, and while standards exist, there are almost as many exceptions as standards. You’ll find format changes even in export functions for the same data.  For this same data set XML returns a different data format than CSV.  You can find a full table of possible components for the formatting string in the Unicode Technical Standard #35. To code the dateFormat string, I looked at the XML file in my browser finding this:

 <date>2016-04-01T06:00:00</date>

I used that format for my dateFormat, setting it to "yyyy-MM-dd'T'HH:mm:ss". If i had set it wrong, or the date format changes in the file, the dateFromString method returns nil. I assign the returned value of dateFromString in bikeRow.

When element is row, bikeRow gets added to the model. Because we set all the properties of the class  BikeRow as optionals, we defer any error checking of our model. Suppose we have XML like this:

<response>
    <row>
        <date>2016-04-01T06:00:00</date>
       <fremont_bridge_sb>43</fremont_bridge_sb>
    </row>
    <row>
         <date>4/1/16 06:00:00AM</date>
         <fremont_bridge_nb>Two Hundred Fifty Six</fremont_bridge_nb>
    </row>
    <row>
    </row>
</response>

All these lines would process, but there would be nil in some fields. The first row would have bikeRow.northBound  as nil since it is missing. The second row would have nil for all three properties since one is missing and two cannot be converted to the correct type. The third has nil for all three properties since there is a <row> directly after a </row> .

I chose this data set so those errors wouldn’t happen, but is always good to be ready for them.

Get ready to run

Our last step is start this up in viewDidLoad. Change viewDidLoad to this, which I strongly suggest copying and pasting to get the URL right.

   override func viewDidLoad() {
        super.viewDidLoad()
        
        //test data
        bikeModel.data += [BikeRow(date: NSDate(),northBound: 12, southBound: 24)]
        bikeModel.data += [BikeRow(date: NSDate(),northBound: 36, southBound: 14)]
        let urlString = "https://data.seattle.gov/resource/4xy5-26gy.xml?$where=date%20between%20%272016-04-01T00:00:00%27%20and%20%272016-04-30T23:00:00%27&$order=date"
        beginParsing(urlString)
    }

Many open data sources like this use the Socrata platform. One nice thing about this platform is a subset of SQL you can append to a databse URL to return a query instead of the whole dataset. The URL sent

SELECT * 
WHERE Date BETWEEN '2016-04-01T00:00:00' AND '2016-04-30T23:00:00'
ORDER BY date

Which gives me all data in April 2016 sorted in time order as an XML file. All I need to do is send that url string to the beginParsing method and the magic happens.

Build and run. In the console, you’ll get this

Beginning tag: 
Beginning tag: 
Beginning tag: 
Beginning tag: 
Added to make 2016-04-01T00:00:00
ending tag:  with contents:2016-04-01T00:00:00
Beginning tag: 
Added to make 6
ending tag:  with contents:6
Beginning tag: 
Added to make 7
ending tag:  with contents:7
ending tag:  with contents:7
model has 

and so on. We can see the delegate is working properly. Once that completes, the simulator gets a very colorful display.

2016-06-03_06-38-24

scroll down to Friday afternoon and you’ll see this:

2016-06-03_06-38-40

This is consistent with what I’m expecting from this data: Rush hours being busy. In another lesson, we charted this data for the month in Excel. One thing we found with that chart was Seattle bike riders are commuters, not recreational riders. We zoomed in on a single day to find that there are rush hour peaks.

2016-05-25_07-42-35

Downtown Seatle is to the south of the Fremont Bridge, while north of the bridge is mostly residential. Yet, the data shows the reverse commute more common:the majority of the traffic is northbound in the mornings and southbound in the afternoons. To make sure there is not a data error, I check the app against the raw data, which you can find here. Looking at some data points, we can see they match correctly.

2016-06-03_06-51-39

While that’s seems wrong, this shows that it’s not our app but the data that shows this trend, as the chart  also shows. I have a few guesses about why that’s the case. Adobe and Google have offices on the north bank of the canal, and it’s safer to store a bike at work in Fremont than downtown, making riding to Fremont more attractive. ( I’d love an opinion or two from Seattlelites why they think this is the pattern in the comments. )

With what we’ve learned here, you can now read and parse simple XML files from data on the web. XML has more power than that. We can build flat-file and relational databases and store them on our device. Our simple data set was made by automated systems and not subject to the kinds of errors you get with human entry. In our next few lessons, we’ll look at both those issues as we continue to explore XML.

The Whole Code

ViewController.swift

//
//  ViewController.swift
//  FremontBicycleXMLDemo
//
//  Created by Steven Lipton on 6/1/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit

class ViewController: UITableViewController, NSXMLParserDelegate {

    var bikeModel = BikeModel()
    var bikeRow = BikeRow()
    var currentContent = String()
    
    
    
    
    //MARK: - XML Parsing Code
    func beginParsing(urlString:String){
        guard let myURL = NSURL(string:urlString) else {
            print("URL not defined properly")
            return
        }
        guard let parser = NSXMLParser(contentsOfURL: myURL) else {
            print("Cannot Read Data")
            return
        }
        parser.delegate = self
        bikeModel = BikeModel()
        if !parser.parse(){
            print("Data Errors Exist:")
            let error = parser.parserError!
            print("Error Description:\(error.localizedDescription)")
            print("Error reason:\(error.localizedFailureReason)")
            print("Line number: \(parser.lineNumber)")
        }
        tableView.reloadData()
    }
    
    //MARK: NSXMLParser delegates
    func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]){
        print("Beginning tag: <\(elementName)>")
        if elementName == "row"{
            bikeRow = BikeRow()
        }
        currentContent = ""
    
    }
    
    //the middle of an element
    //append the string for the element
    func parser(parser: NSXMLParser, foundCharacters string: String){
        currentContent += string
        print("Added to make \(currentContent)")
    }
    
    //the end of an element
    //cast the string to the proper type and store it
    func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?){
        print("ending tag: </\(elementName)> with contents:\(currentContent)")
        switch elementName{
        case "row":
            bikeModel.addRow(bikeRow)
            print ("model has \(bikeRow)")
        case"fremont_bridge_nb":
            bikeRow.northBound = Int(currentContent)
        case "fremont_bridge_sb":
            bikeRow.southBound = Int(currentContent)
        case "date":
            let dateFormatter = NSDateFormatter()
            dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
            bikeRow.date = dateFormatter.dateFromString(currentContent)
        default:
            return
        }
    }
    

    //MARK: - Table View Delegates and Data Sources
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return bikeModel.data.count
    }
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
        let row = indexPath.row
        let nb = bikeModel.data[row].northBound
        let sb = bikeModel.data[row].southBound
        let date = bikeModel.data[row].date
        let backgroundColor = backgroundColorForRow(row)
        cell.backgroundColor = backgroundColor

        //Bridge data
        let counts = String(format:"NorthBound: %i SouthBound: %i",nb,sb)
        cell.textLabel?.text = counts
        cell.textLabel?.backgroundColor = backgroundColor
        //Date
        let dateFormatter = NSDateFormatter()
        dateFormatter.dateStyle = .FullStyle
        dateFormatter.timeStyle = .ShortStyle
        let dateString = dateFormatter.stringFromDate(date)
        cell.detailTextLabel?.text = dateString
        cell.detailTextLabel?.backgroundColor = backgroundColor
                return cell
    }
    
    //MARK: Instance Methods
    func backgroundColorForRow(row:Int)-> UIColor{
        let nb = bikeModel.data[row].northBound
        let sb = bikeModel.data[row].southBound
        
        if nb > sb {
            let tint =  1.0 - CGFloat(nb) / CGFloat(bikeModel.maxNorthBound)
            return UIColor(red: tint, green: tint, blue: 1.0, alpha: 1.0)
        } else {
            let tint = 1.0 - CGFloat(sb) / CGFloat(bikeModel.maxSouthBound)
            return UIColor(red: 1.0,green:tint,blue: tint,alpha: 1.0)
        }
    }
    
    //MARK: - Life cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //test data
        bikeModel.addRow(BikeRow(date: NSDate(),northBound: 12, southBound: 24))
        bikeModel.addRow(BikeRow(date: NSDate(),northBound: 36, southBound: 14))
        let urlString = "https://data.seattle.gov/resource/4xy5-26gy.xml?$where=date%20between%20%272016-04-01T00:00:00%27%20and%20%272016-04-30T23:00:00%27&$order=date"
        beginParsing(urlString)
    }
}


BikeModel.swift

//
//  BikeModel.swift
//  FremontBicycleXMLDemo
//
//  Created by Steven Lipton on 6/1/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit

class BikeRow:NSObject {
    var date:NSDate! = NSDate()
    var northBound:Int! = nil
    var southBound:Int! = nil
    override init(){}
    init(date:NSDate,northBound:Int,southBound:Int){
        self.date = date
        self.northBound = northBound
        self.southBound = southBound
    }
}

class BikeModel: NSObject {
    var data:[BikeRow] = []
    var maxSouthBound = 0
    var maxNorthBound = 0
    func addRow(row:BikeRow){
        data += [row]
        if row.northBound > maxNorthBound{
            maxNorthBound = row.northBound
        }
        if row.southBound > maxSouthBound{
            maxSouthBound = row.southBound
        }

    }
}

One response to “How to Read XML Files From the Web in Swift”

  1. How to parse zipped XML from web url?

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 )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: