Learn Objective-C, Building an App (Part 12): Working With Web Data

Apps become a lot more interesting when we connect them to the internet. They’re able to fetch live data, update that data, and interact with other people and devices. In this post, we’ll look at how to pull data from the internet.

We’ll be working with JSON data. JSON, which stands for JavaScript Object Notation, is one of two popular formats for reading general data from the web (XML is the other). JSON has the advantage of being a lot more terse than XML, and is a lot easier for websites themselves to work with. iOS includes libraries to work with both data types.

Let’s begin by creating a new project—I’ll call it JSONRead—and making our main view controller a subcontroller of UITableViewController. We’ll begin by reading JSON data from the web, parsing it, and saving it to a data object in our controller. In fact, here is the JSON we’ll be using (see below for more on the JSON syntax and how to generate it):

  1. [
  2.     {
  3.         "fname": "Nadene",
  4.         "lname": "Feehan",
  5.         "email": "nadene.feehan@gmail.com",
  6.         "phone": "(152) 555-5321"
  7.     },
  8.     {
  9.         "fname": "Marleen",
  10.         "lname": "Harding",
  11.         "email": "marleen@mharding.com",
  12.         "phone": "(134) 555-1134"
  13.     },
  14.     {
  15.         "fname": "Moon",
  16.         "lname": "Larusso",
  17.         "email": "lunarguy@hotmail.com",
  18.         "phone": "(123) 456-5432"
  19.     },
  20.     {
  21.         "fname": "Scotty",
  22.         "lname": "Wollman",
  23.         "email": [
  24.             "scotty@wollman.com",
  25.             "beammeup@gmail.com",
  26.             "s.wollman@bigcorp.com"
  27.         ],
  28.         "phone": "(152) 555-5321"
  29.     },
  30.     {
  31.         "fname": "Celest",
  32.         "lname": "Feehan",
  33.         "email": "celestial@gmail.com",
  34.         "phone": "(098) 765-4321"
  35.     },
  36.     {
  37.         "fname": "Saggarth",
  38.         "lname": "Ramakristhan",
  39.         "email": "sagar@gmail.com",
  40.         "phone": "(012) 345-4321"
  41.     }
  42. ]

Next, in the .m file, we’ll define a link to the file, and a private interface to hold our properties:

  1. #define JSON_FILE_URL @"https://raw.githubusercontent.com/Binpress/learn-objective-c-in-24-Days/master/Working%20With%20Web%20Data/JSONRead.json"
  3. @interface PeopleListViewController ()
  4. @property (strong, nonatomic) NSArray *names;
  5. @property (strong, nonatomic) NSArray *data;
  6. @end

Synthesize these properties. Next, we’ll load the data and parse it into basic data types in viewDidLoad

  1. - (void)viewDidLoad {
  2.     [super viewDidLoad];
  3.     self.title = @"JSONRead";
  5.     // Download JSON
  6.     NSData *JSONData = [NSData dataWithContentsOfURL:[NSURL URLWithString:JSON_FILE_URL]];
  7.     // Parse JSON
  8.     NSArray *jsonResult = [NSJSONSerialization JSONObjectWithData:JSONData options:kNilOptions error:nil];
  9.     self.data = jsonResult;
  10.     NSMutableArray *_names = [NSMutableArray array];
  11.     for (id item in jsonResult)
  12.         [_names addObject:[NSString stringWithFormat:@"%@ %@", item[@"fname"], item[@"lname"]]];
  13.     self.names = _names;
  14. }

Downloading data from the internet starts by creating an NSURL (which can be instantiated from an NSString), then getting an instance of NSData from that URL. We then use a method that Apple has provided since iOS 5.0 to parse the JSON. Note that we’re casting to NSArray—NSDictionary is another common option, and we’ll talk about how to tell which to use below. Finally, we simply loop through each element in the array, and pull out certain properties to generate an array of names, which we display in our table view. In this case, each element in the parsed array is a dictionary, so we’re using the new literal syntax to access a key-value pair in each item.

Our table view data source is pretty simple—one section, with as many rows as we have names. Our table view cells display the names that correspond to the indexPath.row, and in the delegate, when you select a cell we create a detail view controller (make sure to import the header), pass in the item from our data array that corresponds to the selected indexPath.row, and push the detail view controller onto the navigation stack.

  1. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
  2.     // Return the number of sections.
  3.     return 1;
  4. }
  6. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  7.     // Return the number of rows in the section.
  8.     return [self.names count];
  9. }
  11. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  12. {
  13.     static NSString *CellIdentifier = @"Cell";
  14.     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  15.     if (!cell)
  16.         cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
  18.     cell.textLabel.text = self.names[indexPath.row];
  20.     return cell;
  21. }
  23. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  24.     // Navigation logic may go here. Create and push another view controller.
  25.     PeopleDetailsViewController *detailViewController = [[PeopleDetailsViewController alloc] initWithStyle:UITableViewStyleGrouped];
  26.     detailViewController.details = self.data[indexPath.row];
  27.     [self.navigationController pushViewController:detailViewController animated:YES];
  28. }

JSON Format

JSON begins with key-value pairs:

  1. "fname": "Nadene"

These values can be an integer or floating point, string, boolean (true or false), array, object, or null. A list of key-value pairs is separated by a comma.

JSON values can be an array—a list of values (any of the above values are valid in an array) wrapped in square brackets. Our JSON sample is in fact a single array, as shown by the starting and ending brackets, and this knowledge allows us to choose NSArray as the object that the JSON gets parsed to.

JSON values can also be an object (objects in a Javascript context), which is rather like a grouped set of key-value pairs, wrapped in curly braces. Groups are used in our sample JSON to group the key-value pairs of each person as a separate element inside the array.

So for our sample JSON, we have a single array containing six objects. Each object has four key-value pairs, and for Scotty, he has an array for his email addresses, signifying that he has multiple emails.

Detail view

The details view controller is also a subclass of UITableViewController, with an NSDictionary for the details which we pass in:

  1. @interface PeopleDetailsViewController : UITableViewController
  2. @property (strong, nonatomic) NSDictionary *details;
  3. @end

We define a simple method to generate a name based on the person’s first name and last name

  1. - (NSString *)name {
  2.     return [NSString stringWithFormat:@"%@ %@", self.details[@"fname"], self.details[@"lname"]];
  3. }

and use it to set the title in viewDidLoad

  1. - (void)viewDidLoad {
  2.     [super viewDidLoad];
  3.     self.title = [self name];
  4. }

Our table view data source is, again, pretty simple—1 section, 3 rows, and specific content for each row:

  1. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
  2.     // Return the number of sections.
  3.     return 1;
  4. }
  6. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  7.     // Return the number of rows in the section.
  8.     return 3;
  9. }
  11. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  12.     static NSString *CellIdentifier = @"Cell";
  13.     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  14.     if (!cell)
  15.         cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
  17.     switch (indexPath.row) {
  18.         case 0:
  19.             cell.textLabel.text = [self name];
  20.             cell.detailTextLabel.text = @"Name";
  21.             break;
  22.         case 1: {
  23.             NSString *email = [details objectForKey:@"email"];
  24.             if (!email)
  25.                 email = @"No email";
  26.             if ([email isKindOfClass:[NSArray class]])
  27.                 email = @"<Multiple emails>";
  28.             cell.textLabel.text = email;
  29.             cell.detailTextLabel.text = @"Email";
  30.             break;
  31.         }
  32.         case 2:
  33.             cell.textLabel.text = self.details[@"phone"];
  34.             cell.detailTextLabel.text = @"Phone";
  35.             break;
  36.         default:
  37.             break;
  38.     }
  40.     return cell;
  41. }

This is all the code we need. Click Run, and we’ll see a simple contact manager.

Generating JSON

A JSON string is valid Javascript syntax, so it can be used as a Javascript object or array. To turn a string into Javascript, use JSON.parse(text, function(key, value)); to convert an object into a JSON string, use JSON.stringify(object). jQuery also allows you to serialize() a form, which you can then pass to a server. This is really useful when you want to submit a form via AJAX.

In Ruby, the new hash notation is the same as JSON, which makes things easier. object.to_json will create a JSON string out of an object, and JSON.parse(object) will create an object from JSON.

In PHP, jsonencode() takes an object or array (linear or associative) and returns a JSON string. jsondecode() will return an object or linear array depending on the JSON; an optional second parameter allows you to force an associative array where it would otherwise return an object.

Download project files.

This post is part of the Learn Objective-C in 24 Days course.

Author: Feifan Zhou