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.
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 icon on the lower left of the storyboard. Select the View Controller Scene on the outline.
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
In the Identity inspector, change the Class to View Controller
In the document outline, open up the View Controller Scene until you can select the Table View Cell
In the properties inspector, change the Style to Subtitle and the Identifier to Cell
We’ve set up the storyboard for a table view. Everything else will be code.
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)
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.
scroll down to Friday afternoon and you’ll see this:
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.
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.
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 } } }
Leave a Reply