by

Since UITableView is so prominent in iOS apps, I felt it would be a good idea do one more useful recipe on the subject. For this Recipe we’ll be creating a UITableview complete with a detail view and a navigation controller. In addition, we’ll populate the table and the subsequent detail view with information retrieved from a web service. For this tutorial, we’ll be using the google places API to retrieve a list of restuarants in a city, display them in a tableview, and show more restaurant details in a detail view.

Assumptions

You have a grasp on Xcode Basics. If not, go get some education here.

I also recommend reading the other two tutorials on creating table Views as I’ll be building on some of the concepts in those tutorials.

Designing The Interface

To start off, we’re going to create a project using the Single View Application Template. Title the project “WebTableView”. On the storyboard, drag a new navigation controller into the scene. Control click from the prototype cell of the table view controller to the view controller and choose push from the segue type.



Now your storyboard should look like this:

Since we just added a navigation controller which includes a table view controller, we’ll want to add a class for the table view controller.

Create a new class in Xcode by pressing the “+” in the bottom left hand corner of the screen and choose “New File” from the popup. Create a new Objective-C class and give a name of “TableViewController” with a subclass of UITableViewController. From the storyboard select the table view controller and change the class to “TableViewController” from the Identity inspector on the right. You should also select the view controller and verify that it’s class is set to “ViewController”.

Next we’ll want to set up the tableview controller to conform to the UITableViewDataSource as we did in past UITableView tutorials. Modify tableViewController.h as follows:

1

2

3

4

#import <UIKit/UIKit.h>



@interface TableViewController : UITableViewController <UITableViewDataSource>

@end

As we did in the past UITableView Tutorials we’ll be using the following datasource methods in the TableViewController.m file, since we’ll be using the prepare for segue we won’t be using any delegate methods:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

#pragma mark - Table view data source



- ( NSInteger ) numberOfSectionsInTableView : ( UITableView * ) tableView

{

// Return the number of sections.

return 0 ;

}



- ( NSInteger ) tableView : ( UITableView * ) tableView numberOfRowsInSection : ( NSInteger ) section

{

// Return the number of rows in the section.

return 0 ;

}



- ( UITableViewCell * ) tableView : ( UITableView * ) tableView cellForRowAtIndexPath : ( NSIndexPath * ) indexPath

{

static NSString * CellIdentifier = @ "Cell" ;

UITableViewCell * cell = [ tableView dequeueReusableCellWithIdentifier : CellIdentifier forIndexPath : indexPath ] ;



// Configure the cell...



return cell;

} NSIntegernumberOfSectionsInTableViewUITableViewtableViewNSIntegertableViewUITableViewtableView numberOfRowsInSectionNSIntegersectionUITableViewCelltableViewUITableViewtableView cellForRowAtIndexPathindexPathCellIdentifierUITableViewCellcelltableView dequeueReusableCellWithIdentifierCellIdentifier forIndexPathindexPathcell;

You’ll notice that since we created the new class that is subclassed by the UITableViewController, These methods were already set up for us. You’ll also notice a bunch of commented common methods and hints. You can go ahead and remove the commented out methods as well as the delegate method.

Make sure you select the prototype cell from the storyboard and set the reuse identifier to “Cell” from the Attributes Inspector on the right

The next step is setting up AFNetworking for handling the request and parsing out the JSON. Alternatively, we could have used NSURLConnection directly, but the industry has pretty much standardized on AFNetworking and it is much easier to implement. For a tutorial on NSURLConnection Check out Recipe 4.

Setting Up The Requests

First, You’ll need to add AFNetworking to your project. You can do this by downloading the AFNetworking package from github and adding the files manually, or by installing cocoapods and doing it that way. Check out this guide to get you up and running with AFNetworking: Getting Started With AFNetworking

Once you have downloaded AFNetworking and added it to your project, import it at the top of the TableViewController.m implementation file.

1

2

#import "TableViewController.h"

#import "AFNetworking.h"

We’ll go ahead and create a method for making the HTTP request titled “makeRestuarantsRequests” after the didRecieveMemoryWarning method. We’ll also go ahead and fill it in with the request code for AFNetworking.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

- ( void ) makeRestuarantRequests

{

NSURL * url = [ URLWithString : @ "A URL which returns JSON" ] ;



NSURLRequest * request = [ requestWithURL : url ] ;

//AFNetworking asynchronous url request

AFJSONRequestOperation * operation = [ AFJSONRequestOperation

JSONRequestOperationWithRequest : request

success :^ ( NSURLRequest * request, * response, id responseObject )

{

NSLog ( @ "JSON RESULT %@" , responseObject ) ;



}

failure :^ ( NSURLRequest * request, * response, * error, id responseObject )

{

NSLog ( @ "Request Failed: %@, %@" , error, error.userInfo ) ;

} ] ;



[ operation start ] ;



} makeRestuarantRequestsurl NSURL URLWithStringrequest NSURLRequest requestWithURLurlAFJSONRequestOperationoperationAFJSONRequestOperationJSONRequestOperationWithRequestrequestsuccessrequest, NSHTTPURLResponse response,responseObjectNSLog, responseObjectfailurerequest, NSHTTPURLResponse response, NSError error,responseObjectNSLog, error, error.userInfooperation start

In this Code we first create a NSURL object and set it to the URL that we want to make the JSON request from. Then using that URL we create a NSURLRequest object. Using both of these objects we call the AFJSONRequestOperation class to make the HTTP request and parse the JSON we receive.

For this example we’re using JSON, but if you wanted to receive XML or PLIST data from a web server just replace “JSON” with either “XML” or “ProperyList” in AFJSONRequestOperation, JSONRequestOperationWithRequest, and the success block.

The AFJSONRequestOperation class method receives three arguments: A request, a success block, and a failure block. The blocks are denoted by the “^” instead of the standard “*” for a pointer. AFNetworking will perform the request on separate thread (i.e. not the main thread) and then call either the success block (if the request is successful) or the failure block (if the request fails) back on the main thread. Because either of these completion blocks are performed on the main thread, it is safe to add code that interacts with UIKit in either of these blocks.

For this example we’re using the Google places API. You’ll have to sign up for a Google Places API key which will allow 1000 request a day. Follow the instructions here to get signed up so you can get an API key Google Places API Getting Started.

One of the Google examples uses the following request:

https://maps.googleapis.com/maps/api/place/textsearch/json?query=restuarants+in+sydney&sensor=false&key=Your_Key_Here

The Base URL here is https://maps.googleapis.com/maps/api/place/textsearch/json

Then there is a “?”. This denotes the beginning of our arguments. Each subsequent argument is seperated by “&”.

So in this example we are using the JSON Places API to search for “restaurants in Sydney”, We are not using data from a GPS device so sensor = false, and of course Google want our authentication key to make this request.

If you were to type this whole thing into your web browser you’ll get back a bunch of JSON formatted data that looks like this:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

{

"html_attributions" : [ ] ,

"next_page_token" : "CkQ0AAAANLLiCozQVXZAA2mRgg3yeG6LzdQM0-w1D54F2d_- vDUC2xpthn2jf1aUmV1IxYMVY01jO9G8GgqhtM9iF0QJOxIQHin8zi8dVIYt3oFYzBOOCxoUXIIhOkFKi2h51CRLQZ5ENpqN23E" ,

"results" : [

{

"formatted_address" : "Upper Level, Overseas Passenger Terminal, The Rocks NSW, Australia" ,

"geometry" : {

"location" : {

"lat" : - 33.8583790 ,

"lng" : 151.2100270

}

} ,

"icon" : "http://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png" ,

"id" : "f181b872b9bc680c8966df3e5770ae9839115440" ,

"name" : "Quay" ,

"opening_hours" : {

"open_now" : true

} ,

"photos" : [

{ ………

Which is exactly what you should get if you replace “Url which Returns JSON” in the makeRestuarantRequest with this Google API formated URL.

Go ahead and make the following changes to the makeRestuarantRequest:

1

NSURL * url = [ URLWithString : @ "https://maps.googleapis.com/maps/api/place/textsearch/json?query=restuarants+in+sydney&sensor=false&key=Your Api Key" ] ; url NSURL URLWithString

Make sure you put your API key in.

And call the makeRestuarantRequest method from the viewDidLoad method

1

2

3

4

5

- ( void ) viewDidLoad

{

[ super viewDidLoad ] ;

[ self makeRestuarantsRequests ] ;

}

Now if you run it you’ll see the JSON populate in the output window.

Next Edit your TableViewController.h as follows:

1

2

3

4

5

6

7

8

#import <UIKit/UIKit.h>



@interface TableViewController : UITableViewController <UITableViewDataSource>



@property ( strong, nonatomic ) NSArray * googlePlacesArrayFromAFNetworking;

@property ( strong, nonatomic ) NSArray * finishedGooglePlacesArray;



@end TableViewControllerUITableViewController strong, nonatomicgooglePlacesArrayFromAFNetworking;strong, nonatomicfinishedGooglePlacesArray;

Then update the ViewDidLoadMethod in the TableViewController.m file like so:

1

2

3

4

5

6

- ( void ) viewDidLoad

{

[ super viewDidLoad ] ;

self.finishedGooglePlacesArray = [ [ alloc ] init ] ;

[ self makeRestuarantsRequests ] ;

} viewDidLoadsuper viewDidLoadself.finishedGooglePlacesArray NSArray allocinitself makeRestuarantsRequests

Update the makeRestuarantsRequest:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

- ( void ) makeRestuarantsRequests

{

NSURL * url = [ URLWithString : @ "https://maps.googleapis.com/maps/api/place/textsearch/json?query=restuarants+in+sydney&sensor=false&key=Your_API_Key" ] ;



NSURLRequest * request = [ requestWithURL : url ] ;



AFJSONRequestOperation * operation = [ AFJSONRequestOperation

JSONRequestOperationWithRequest : request

success :^ ( NSURLRequest * request, * response, id responseObject )

{



self.googlePlacesArrayFromAFNetworking = [ responseObject objectForKey : @ "results" ] ;



}

failure :^ ( NSURLRequest * request, * response, * error, id responseObject )

{

NSLog ( @ "Request Failed with Error: %@, %@" , error, error.userInfo ) ;

} ] ;



[ operation start ] ;

} makeRestuarantsRequestsurl NSURL URLWithStringrequest NSURLRequest requestWithURLurlAFJSONRequestOperationoperationAFJSONRequestOperationJSONRequestOperationWithRequestrequestsuccessrequest, NSHTTPURLResponse response,responseObjectself.googlePlacesArrayFromAFNetworkingresponseObject objectForKeyfailurerequest, NSHTTPURLResponse response, NSError error,responseObjectNSLog, error, error.userInfooperation start

Here we’re storing the results of the JSON request into the googlePlacesArrayFromAFNetworking array which was allocated and initialized in the viewDidLoadMethod.

Setting Up The DataSource

OK! The rest should look fairly familiar! First we’ll start off by setting the numberOfSectionsInTableView to have a return of 1 since we only want 1 section:

1

2

3

4

5

- ( NSInteger ) numberOfSectionsInTableView : ( UITableView * ) tableView

{

// Return the number of sections.

return 1 ;

}

Now we’ll update the numberOfRowsInSection method to return the amount of places returned by the API result:

1

2

3

4

5

- ( NSInteger ) tableView : ( UITableView * ) tableView numberOfRowsInSection : ( NSInteger ) section

{

// Return the number of rows in the section.

return [ self.googlePlacesArrayFromAFNetworking count ] ;

}

Last but not least we’ll update the cellForRowAtIndexPath method:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

- ( UITableViewCell * ) tableView : ( UITableView * ) tableView cellForRowAtIndexPath : ( NSIndexPath * ) indexPath

{

static NSString * CellIdentifier = @ "Cell" ;

UITableViewCell * cell = [ tableView dequeueReusableCellWithIdentifier : CellIdentifier forIndexPath : indexPath ] ;



NSDictionary * tempDictionary = [ self.googlePlacesArrayFromAFNetworking objectAtIndex : indexPath.row ] ;



cell.textLabel.text = [ tempDictionary objectForKey : @ "name" ] ;



if ( [ tempDictionary objectForKey : @ "rating" ] != NULL )

{

cell.detailTextLabel.text = [ stringWithFormat : @ "Rating: %@ of 5" , [ tempDictionary objectForKey : @ "rating" ] ] ;

}

else

{

cell.detailTextLabel.text = [ stringWithFormat : @ "Not Rated" ] ;

}



return cell;

} UITableViewCelltableViewUITableViewtableView cellForRowAtIndexPathindexPathCellIdentifierUITableViewCellcelltableView dequeueReusableCellWithIdentifierCellIdentifier forIndexPathindexPathtempDictionaryself.googlePlacesArrayFromAFNetworking objectAtIndexindexPath.rowcell.textLabel.texttempDictionary objectForKeytempDictionary objectForKeycell.detailTextLabel.text NSString stringWithFormattempDictionary objectForKeycell.detailTextLabel.text NSString stringWithFormatcell;

here we are creating a dictionary object for each item in the googlePlacesArrayFromAFNetworking array. Then we are setting the cell label and detail labels using those values. If the rating is null, we return not rated for the detail label text value.

Now we’ll want to connect the dataSource outlet to the table view controller:

Now if you run it is should work right? Not quite. The Table view probably looks blank huh? That’s where that asynchronous request operation comes in. The table data source methods are loading before there is any information received from the request. To fix this, reload the table in the success block. This will refresh the table view after the data is loaded.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

#pragma mark - AFNetworking



- ( void ) makeRestuarantsRequests

{

NSURL * url = [ URLWithString : @ "https://maps.googleapis.com/maps/api/place/textsearch/json?query=restuarants+in +sydney&sensor=false&key=Your_API_Key" ] ;



NSURLRequest * request = [ requestWithURL : url ] ;



AFJSONRequestOperation * operation = [ AFJSONRequestOperation

JSONRequestOperationWithRequest : request

success :^ ( NSURLRequest * request, * response, id responseObject )

{

self.googlePlacesArrayFromAFNetworking = [ JSON objectForKey : @ "results" ] ;

[ self.tableView reloadData ] ;

}

failure :^ ( NSURLRequest * request, * response, * error, id responseObject )

{

NSLog ( @ "Request Failed with Error: %@, %@" , error, error.userInfo ) ;

} ] ;



[ operation start ] ;

} makeRestuarantsRequestsurl NSURL URLWithStringrequest NSURLRequest requestWithURLurlAFJSONRequestOperationoperationAFJSONRequestOperationJSONRequestOperationWithRequestrequestsuccessrequest, NSHTTPURLResponse response,responseObjectself.googlePlacesArrayFromAFNetworkingJSON objectForKeyself.tableView reloadDatafailurerequest, NSHTTPURLResponse response, NSError error,responseObjectNSLog, error, error.userInfooperation start

Alright, Now you should have a result that looks like this:

The last thing to do here is set up the prepareForSegue method. I’ll go ahead and add the code down here and then explain it:

1

2

3

4

5

6

7

8

#pragma mark - Prepare For Segue



- ( void ) prepareForSegue : ( UIStoryboardSegue * ) segue sender : ( id ) sender

{

NSIndexPath * indexPath = [ self.tableView indexPathForSelectedRow ] ;

ViewController * detailViewController = ( ViewController * ) segue.destinationViewController;

detailViewController.restuarantDetail = [ self.googlePlacesArrayFromAFNetworking objectAtIndex : indexPath.row ] ;

} prepareForSegueUIStoryboardSeguesegue sendersenderindexPathself.tableView indexPathForSelectedRowViewControllerdetailViewControllerViewControllersegue.destinationViewController;detailViewController.restuarantDetailself.googlePlacesArrayFromAFNetworking objectAtIndexindexPath.row

Here we are creating an NSIndexpath object and set it to the index path for the selected row. Then we select the item in the googlePlacesArrayFromAFNetworking array and choose the item for the selected row at index path. Then we set the NSDictionary Object “restuarantDetail”in the ViewConteroller class to this result.

Notice were creating an instance of the ViewController class, so you’ll need to import the ViewController.h file at the top of the TableViewController.m file.

1

2

3

#import "TableViewController.h"

#import "ViewController.h"

#import "AFNetworking.h"

Now update ViewController.h and .m files as follows:

1

2

3

4

5

6

7

8

9

10

11

#import <UIKit/UIKit.h>



@interface ViewController : UIViewController



@property ( strong, nonatomic ) NSDictionary * restuarantDetail;



@property ( strong, nonatomic ) IBOutlet UILabel * restuarantNameLabel;

@property ( strong, nonatomic ) IBOutlet UIImageView * restuarantImageView;

@property ( strong, nonatomic ) IBOutlet UITextView * restuarantAddressView;



@end ViewControllerUIViewControllerstrong, nonatomicrestuarantDetail;strong, nonatomicIBOutlet UILabelrestuarantNameLabel;strong, nonatomicIBOutlet UIImageViewrestuarantImageView;strong, nonatomicIBOutlet UITextViewrestuarantAddressView;

Change the ViewController.m viewDidLoad method as follows:

1

2

3

4

5

6

7

8

9

- ( void ) viewDidLoad

{

[ super viewDidLoad ] ;

// Do any additional setup after loading the view, typically from a nib.



self.restuarantNameLabel.text = [ self.restuarantDetail objectForKey : @ "name" ] ;

[ self.restuarantImageView setImageWithURL : [ URLWithString : [ self.restuarantDetail objectForKey : @ "icon" ] ] ] ;

self.restuarantAddressView.text = [ self.restuarantDetail objectForKey : @ "formatted_address" ] ;

} viewDidLoadsuper viewDidLoadself.restuarantNameLabel.textself.restuarantDetail objectForKeyself.restuarantImageView setImageWithURL NSURL URLWithStringself.restuarantDetail objectForKeyself.restuarantAddressView.textself.restuarantDetail objectForKey

Now you should be able to select one of the table view items and view the restaurant icon and address.

Alright! now you should have a good idea of how to load data from the web into a UITableView. In a real application, you would most likely have a class that would build the URL’s based on a user input.

Hope you enjoyed it!