iOS 8.3+
Swift 1.2
Parse 1.7.2

2015/04/14 — Updated to cover Parse 1.7.2. Further information on the fix process for upgrading from older versions of parse to v1.7.2 can be found here.

iOS + Swift 1.2 + parse.com 1.7.2 / Tutorial — Table View (Part 2 of 3)

Welcome back to part 2 — our focus for this tutorial is on getting data out of Parse and into a table view.

If you have not already read or are not comfortable implementing Parse into your XCode project — be sure to read Part 1 of this tutorial series.

Don’t forget. This tutorial is all about Swift and Parse. If you’re looking for an ObjectiveC introduction to Parse I’m afraid you’re out of luck.

If you have not already completed the project setup and integration of the Parse framework there is a starter project over on GitHub. Let’s start by checking that the project builds — if that’s successful then Hurrah!

As before, there is a shopping list of links at the end of the article.

Project Goal

The key functional capabilities we’re aiming for are:

  • Table View of countries
  • Pull to refresh data from the server
  • Detail view allowing display and update of values
  • Save data back to the server

I am not going to phaff with some of the UI niceties — like shifting the textfields up as the keyboard displays. We are going to focus on the data.

Story Board

It’s best to start this project with a clear story board. So let’s delete any views in the story board and also delete any view controllers in our code.

The project should look like this.

Let’s drag on some components.

  • Table View Controller
  • View Controller
  • Segue from Table View Controler to the View Controller
  • Tool bar at the base of the View Controller with a default button titled “Save”

And embed them both in a Navigation Controller — remembering to make the navigation controller the Initial View.

Your story board should now show a navigation controller which is set to display initially. A Table View that has a segue to a detail view controller. The detail view controller should have an embedded tool bar with the default button titled “Save”.

Prototype Cell

Let’s deal with the table view prototype cell.

We need to set the class type to “PFTableViewCell”. This is a class provided by the Parse framework.

Like all prototype cells we need to select the style type and give the cell a name. We’ll select a style of “Subtitle” and give the cell an identifier “Cell”

Table View Controller

Let’s create a controller class file for our table view. Add a new file to the project.

  • OS = iOS
  • Type = Cocoa Touch class
  • File name = TableViewController
  • Subclass = PFQueryTableViewController — You will need to type this into the subclass field
  • Language = Swift

Let’s not forget to wire up the controller class to the table view in our story board.

Initialisers

The resulting new file should contain an empty class definition. Add the following to the top of the file.

The key elements we need to pay attention to are in the second, convenience, initialiser.

Our Parce data class name

self.parseClassName = “Countries” 

The column we wish to display

self.textKey = “nameEnglish” 

Pull to refresh data from server enable or disable

self.pullToRefreshEnabled = true 

Table pagination enable or disabled

self.paginationEnabled = false

If you have pagination enabled (true) then you also need to set the number of rows to fetch from the server.

self.objectsPerPage = 20

PFQuery

We need to define the query parameters Parse will use when connecting to the server data set. Add the following method to TableViewController.

The key lines are :

 var query = PFQuery(className: “Countries”)
query.orderByAscending(“nameEnglish”)

You could further restrict the returned data set.

 ie : query.whereKey(“currencyCode”, equalTo:”EUR”)

Maybe consider adding a search box to the table view and use the values entered within your query.

First Test

Believe it or not we can now attempt our first run of the app.

We are missing several methods you would normally associate with table view controllers, Parse takes care of detailing how many sections, how many rows and pushing values into the prototype cell. Heck — we don’t even have a viewDidLoad()!

The functionality that should be present is a list of data and pull to refresh.

Completing the Prototype Cell

Right now the table only displays the name of the country in our list. Let’s add the capital city in the subtitle position.

Add the following method to the table view controller.

There is a lot going on in this method so lets step through the code.

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell {

Notice how the method receives a PFObject “object” — the “object” contains all the data from our query for the target cell, so this is how the data is made available to the method — Parse magic. Notice also how the output of the method is a cell of type “PFtableViewCell”.

var cell = tableView.dequeueReusableCellWithIdentifier(“Cell”) as! PFTableViewCell! 
    if cell == nil { 
cell = PFTableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: “Cell”)
}

Here we’re creating a cell that we’ll use to populate the table — of course this is of type PFTableViewCell and not the normal TableViewCell. Notice also that the cell is checked to ensure it exists — suggesting that there are some scenarios where the initial object creation might fail. I’ve been using this code for some time and have not found a time when the initial creation fails, which would cause the covering code to run, so I am not 100% sure why this is recomended by Parse. My suspicion is that the pull to refresh and/or the paging capabilites sometime trigger the second instantiation.

if let nameEnglish = object?[“nameEnglish”] as? String {       
cell?.textLabel?.text = nameEnglish
}
if let capital = object?[“capital”] as? String { 
cell?.detailTextLabel?.text = capital
}

These two groups of code should make immediate sense. We’re checking the optional unwrapping and pushing any values into our prototype cell.

So that’s it. Parse provides an object which contains just the data we need to the method and we’re free to push that data into our table cell.

Second Test

Lets run the app again to check we’re getting both the country name and the capital city in our table view. The segue should also be working — although it’s seguing to a blank screen, but we’ll fix that shortly.

Detail View Controller

Before we can manage the segue we need to create and wire up a controller class for the detail view.

Add a new file to your project:

  • OS = iOS
  • Type = Cocoa Touch class
  • File name = DetailViewController
  • Subclass = UIViewController
  • Language = Swift

Let’s not forget to wire up the controller class to the view in our story board.

Back in the story board lets add some labels and text fields to display and edit our data. Go ahead and add these elements to your story board. Don’t worry about the layout looking exactly like mine — just lay them out as you feel.

Now we’ll wire the textfields into the controller. Add the follwing code to your DetailViewController class.

Don’t forget to DRAG to complete the wiring up.

We need to add a property that will store the “incoming” parse object from the table view controller. Add the following to the top of your detail view controller class file.

// Container to store the view table selected object
var currentObject : PFObject?

When the detail view controller loads we need to take the contents of currentObject and push the values into the appropriate text fields so that they are displayed. Update your viewDidLoad() method to look like this.

We can now return to the table view controller and complete the prepare for segue method.

Segue

Back in our table view controller class, add the follwing method to the class.

The code in this method is pretty much what you would expect for any table.

var detailScene = segue.destinationViewController as! DetailViewController

Create a variable that holds a reference to the destination view controller. If you called your detail view controller something other than DetailViewController — remember to change the ‘as’ object.

if let indexPath = self.tableView.indexPathForSelectedRow() { 
let row = Int(indexPath.row)
detailScene.currentObject = objects?[row] as? PFObject
}

The key line here is detailScene.currentObject = objects[row] as? PFObject where we send the selected Parse object into the detail view controller.

Third Test

Lets run the app again to check segue is working and that the correct country object is displayed in the detail view controller.

Saving data back to the server

Back in the detail view controller it’s time to deal with the Save button.

CTRL=DRAG from the save button to the controller class. Notice that I’m starting the CTRL+DRAG from the story board outliner — it’s much easier to target the interface object.

Fill out the IBAction like so.

Update the IBAction by copying this code into the class method.

The code is pretty much self explanitory. The key line is :

object.saveEventually(nil) 

This line commits your changes and will save them back to the server the next time the app is able to connect. It does this in the background so it doesn’t block the app.

The final line…

self.navigationController?.popViewControllerAnimated(true) 

pops the detail view controller off of the navigation controller, which returns us to the table view. You’ll notice that the data table does not update unless you pull to refresh. If we add this method to the table view controller we can give the users a better experience.

So that’s it. We’ve built an app that stores it’s data in the cloud on a well managed platform. We can view and update the data.

If you want to challenge yourself think about adding the following features.

  • Adding more fields
  • Create new countries in the app

In part 3 of this series I will tackle user authentication and row level ACL — securing data so that users only see their data.

Links

Originally published at www.bizzi-body.com/blog on February 5, 2015. Updated on April 15th to ensure Swift 1.2 compatibility.