How I Used Swift to Put Reddit JSON in an iOS Device

A buddy of mine suggested I try to parse the JSON from Reddit to a table view as an exercise today. I thought this seemed reasonable way to learn Swift and he wanted to use the code for some project. Everybody wins.

I have only been doing Swift for a couple days now and never did Objective-C. I am obviously no expert, and am learning new things every day. I will not be going into the gory details as I am still figuring them out myself. I will however be highlighting gotcha’s and things I learned along the way about Swift.

Anyways onto the geeky stuff…

Setting up our project

The first thing you are going to want to do is setup your project with Xcode 6. I’m using beta 4 for this tutorial.

1
File -> New -> Project

We are making an iOS application, single view, with the Swift language.

images

Next double click on Main.storyboard. This is our window.

Show the document outline. There is a little handle in the lower left window of the storyboard to do this.

images

Highlight our View Controller for this scene, and navigate to the attributes inspector.

images

For this particular project, and because I have an iPhone 4S, I’m going to set the size to be “Retina 4-Inch Full Screen” and I’m going to set my Orientation to Portrait.

This will reduce the number of surprises we will get.

Creating our Table View

From the bottom of the left side of our screen, we want to select the Table View, and drag it into our storyboard. Resize to taste.

images

Next we want to right click, and drag from the Table View to our View Controller. Its the little yellow Icon under our view. Select “dataSource” and then make a second right-click-drag and select “delegate” as well.

images images

This is going to affect which functions need to be implemented in our view controller.

Finally, open up your ViewController.swift and drag the table view into the actual source file.

images

Setting up the ViewController code

Here is my TL:DR code…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
    var tableData = []
    @IBOutlet weak var redditListTableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        getRedditJSON("http://www.reddit.com/.json")
    }
    
    func getRedditJSON(whichReddit : String){
        let mySession = NSURLSession.sharedSession()
        let url: NSURL = NSURL(string: whichReddit)
        let networkTask = mySession.dataTaskWithURL(url, completionHandler : {data, response, error -> Void in
            var err: NSError?
            var theJSON = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as NSMutableDictionary
            let results : NSArray = theJSON["data"]!["children"] as NSArray
            dispatch_async(dispatch_get_main_queue(), {
                self.tableData = results
                self.redditListTableView!.reloadData()
            })
        })
        networkTask.resume()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
        return tableData.count
    }
    
    func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
        let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "MyTestCell")
        let redditEntry : NSMutableDictionary = self.tableData[indexPath.row] as NSMutableDictionary
        cell.textLabel.text = redditEntry["data"]!["title"] as String
        cell.detailTextLabel.text = redditEntry["data"]!["author"] as String
        return cell
    }
}

Lets talk about some of the important parts of our code…

viewDidLoad()

1
2
3
4
5
6
override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    
    getRedditJSON("http://reddit.com/.json")
}

viewDidLoad acts as the entry point into our view controller. We can think of this as main() is it is executed as soon as our view loads. You can see I added to the function our call to getRedditJSON(“http://reddit.com/.json”) which is the front page of reddit in JSON format. Lets just jump to that function.

getRedditJSON()

1
2
3
func getRedditJSON(whichReddit: String){
        let session = NSURLSession.sharedSession()
        let url: NSURL = NSURL(string: whichReddit)

In order to make network calls we need to grab an instance of NSURLSession.sharedSession. It acts as an intermediary between all network calls that our iOS device can make.

I also setup an NSURL for the reddit we want to visit. All library calls I’ve seen to the Cocoa framework start with NS. NS stands for NeXTSTEP which was the original name for OSX before Apple bought it :)
https://en.wikipedia.org/wiki/NeXTSTEP

1
let networkTask = mySession.dataTaskWithURL(url, completionHandler : {data, response, error -> Void in

The function dataTaskWithURL accepts (as you would guess) a url and a task. Here you can see Swift closures in action on dataTaskWithURL’s completionHandler. completionHandler is what gets called after the network action has completed. The task is defined in a closure. The typical syntax for a closure this is:

1
2
3
4
5
{(par,am,meters) -> returnType in
    //code
    //code
    //code
}

Closure’s can do some pretty powerful things. I definitely recommend googling this as well, but for right now just know that its basically like setting up an inline function.

It should also be worth noting, that for clarity, you can set closures to variables, and just pass the variable instead of defining the closure like that. In encourage you to try to refactor this code to do this.

1
2
var err: NSError?
var theJSON = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as NSMutableDictionary

Here is where the real magic happens. Thanks to Cocoa, we can use NSJSONSerialization’s class and call its JSONObjectWithData function. In this case I am using an NSMutableDictionary. The difference between an NSDictionary and this is I can modify the values. We are not going to be modifying anything in this example, but this is helpful if we need to remove a null object.

1
let results : NSArray = theJSON["data"]!["children"] as NSArray

We want to know how many entires there are and we would like to split this into an array of dictionaries we can use.

It is interesting to note that without the ! in this statement, we can bring Xcode to a full crash (at least in beta4). I believe the ! acts as try() would in ruby.

1
2
3
4
dispatch_async(dispatch_get_main_queue(), {
    self.tableData = results
    self.redditListTableView!.reloadData()
})

At the end of this function, we are kicking off a thread to update the tableData with the latest results, and we tell the table view to reload its data. This calls our method: func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {

1
networkTask.resume()

Finally, after we set up this networkTask, we start it with a call to resume()

Now lets look at the functions we implemented from UITableViewDataSource

numberOfRowsInSection

1
2
3
func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
        return tableData.count
}

This function tells our table view how many rows it should make. In this case we are using the tableData’s count. We set up this number when we called our dispatch earlier.

cellForRowAtIndexPath

1
2
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
    let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "MyTestCell")

The interesting thing about this function that I learnt via println debugging, is this function is actually called whenever the view is updated. Also it tends to render things slightly out of view. So when you slide this list up and down, it will always render one of the items that you can’t see in whatever direction you are scrolling.

Think about apps that slow down considerably when you are scrolling really fast. This is because this function is being called for every cell as you scroll.

If the goal of your application is to be responsive, then everything done in here should take advantage of Grand Central Dispatch to populate the data asynchronously. You may also want to think about caching data if you plan on making network calls in here.

For the purposes of this tutorial I am not going to be doing this, but you certainly should read up on this topic.

It is also worth noting the style of cell we are using is “Subtitle” which gives up a subtitle entry per list. In this case I’m going to make the big text the title of the reddit entry and make the detail (subtitle) text the author.

1
2
3
4
5
let redditEntry : NSMutableDictionary = self.tableData[indexPath.row] as NSMutableDictionary
cell.textLabel.text = redditEntry["data"]!["title"] as String
cell.detailTextLabel.text = redditEntry["data"]!["author"] as String
return cell
}

And thats about it :)

Here is what the finished product looks like running in iOS simulator:

images

Much can be done to this application to make it better. We can do a lot more verbose error checking. We can handle null values of the redditEntry better. We can cache and load thumbnails of the reddit threads. I will leave this as an exercise for you to figure out.

Comments