iOS Programming Recipe 16: Populating A UITableView With Data From The Web

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:

Designing The View

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;
}

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 = [NSURL URLWithString:@"A URL which returns JSON"];

NSURLRequest *request = [NSURLRequest requestWithURL:url];
//AFNetworking asynchronous url request
AFJSONRequestOperation *operation = [AFJSONRequestOperation
                            JSONRequestOperationWithRequest:request
                            success:^(NSURLRequest *request, NSHTTPURLResponse *response, id responseObject)
                                     {
                                         NSLog(@"JSON RESULT %@", responseObject);

                                     }
                            failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id responseObject)
                                     {
                                            NSLog(@"Request Failed: %@, %@", error, error.userInfo);
                                     }];

[operation 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 = [NSURL URLWithString:@"https://maps.googleapis.com/maps/api/place/textsearch/json?query=restuarants+in+sydney&sensor=false&key=Your Api Key"];

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

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


1
2
3
4
5
6
- (void)viewDidLoad
{
    [super viewDidLoad];
    self.finishedGooglePlacesArray = [[NSArray alloc] init];
    [self 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 = [NSURL URLWithString:@"https://maps.googleapis.com/maps/api/place/textsearch/json?query=restuarants+in+sydney&sensor=false&key=Your_API_Key"];

    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    AFJSONRequestOperation *operation = [AFJSONRequestOperation
                            JSONRequestOperationWithRequest:request
                            success:^(NSURLRequest *request, NSHTTPURLResponse *response, id responseObject)
                                     {

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

                                     }
                            failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id responseObject)
                                     {
                                            NSLog(@"Request Failed with Error: %@, %@", error, error.userInfo);
                                     }];

    [operation 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 = [NSString stringWithFormat:@"Rating: %@ of 5",[tempDictionary   objectForKey:@"rating"]];
        }  
        else
        {
            cell.detailTextLabel.text = [NSString stringWithFormat:@"Not Rated"];
        }

    return cell;
}

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:

Connecting The Delegate

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 = [NSURL URLWithString:@"https://maps.googleapis.com/maps/api/place/textsearch/json?query=restuarants+in +sydney&sensor=false&key=Your_API_Key"];

    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    AFJSONRequestOperation *operation = [AFJSONRequestOperation
                                JSONRequestOperationWithRequest:request
                                success:^(NSURLRequest *request, NSHTTPURLResponse *response, id responseObject)
                                         {
                                             self.googlePlacesArrayFromAFNetworking = [JSON objectForKey:@"results"];
                                             [self.tableView reloadData];
                                        }
                                failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id responseObject)
                                         {
                                            NSLog(@"Request Failed with Error: %@, %@", error, error.userInfo);
                                            }];

    [operation start];
}

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

Table View Loaded

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];
}

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

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:[NSURL URLWithString:[self.restuarantDetail objectForKey:@"icon"]]];
    self.restuarantAddressView.text = [self.restuarantDetail objectForKey:@"formatted_address"];
}

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

Final Result

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!

About Joe Hoffman

I am An Electrical Engineer that spends a lot of my off time doing web development and other programming. Currently I am trying to expand my knowledge with iOS and I find that writing tutorials helps me learn more thoroughly as well as provide some useful info to others.

Comments

  1. Hi Joe :-)
    Nice tutorial.. Any chance we could see this same one but using our own plist data instead of Googles? Or maybe a tutorial on how to load data asynchronously into a TableView? Ive made an app that retrieves data from a plist but Its jaggidy due to some thumbnail images ive got..

    • The only thing that would be different from what you requested and what I have done in this tutorial is the AFNetworking block changes a little.

      AFPropertyListRequestOperation *operation = [AFPropertyListRequestOperation
                                      PropertyListRequestOperationWithRequest:request
                                      success:^(NSURLRequest *request, NSHTTPURLResponse *response, id responseObject)

      Then of course you may need to change the way that you accept the data. An Array of dictionary objects might be the best option for formatting the PList.

      The Beauty of AFNetworking is the requests are already done asynchronously.

      For Images use the AFNetworking library as well

      UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 100.0f, 100.0f)];
      [imageView setImageWithURL:[NSURL URLWithString:@"http://i.imgur.com/r4uwx.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder-avatar"]];
  2. This is absolutely great, just what I needed to sort out the async->json details in a tableViewController. Thanks!

  3. Marv Howard says:

    The tutorial looks like it’s what I need to learn more about table views, but I’m getting errors when I try to build. Things like undeclared JSON and such. Any chance of a downloadable source to play with? Thanks.

  4. Hi Joe,

    what if we want to take user input in a text field and pass into the query, to fetch results from a web api? in this case, you want take sydney as input and display results!

    I appreciate your help. Thanks!

    • Joe Hoffman says:

      Hi Jay,

      Sorry It’s been a while since you posted this.

      To do that you would want to start with a view that had a text field and a button. When the button is clicked you want to segue to the view we are using and use the prepareForSeque method to pass an NSString which will hold the city name obtained from the text field.

      Then in the new view which contains all of the API logic, I would build the string “https://maps.googleapis.com/maps/api/place/textsearch/json?query=restuarants+in+sydney&sensor=false&key=Your Api Key” using stringWithFormat. eg.

      NSString apiString = [NSString stringWithFormat(@"%@%@%@",@"https://maps.googleapis.com/maps/api/place/textsearch/json?query=restuarants+in+",cityString, @"&amp;sensor=false&amp;key=Your Api Key")];

      Note that the format is a little bit different for city and state. Check the Google maps API documentation for that.

      -Joe

      • Wow! great to see your reply. Actually, my app is almost same, but I am taking user input, passing it in the url and fetching results on a button click. Displaying results in the table view controller. I have text box, button and tableview on the same screen. this is how I am calling my Url

        NSString *urlStr =[NSString stringWithFormat:@”http://devapi.nutritionix.com/v1/api/item/?query=%@&appId=b3bf1cb4&appKey=9d3cb881d458d1ef48586d33e83a8835″, query];

        NSURL *url = [NSURL URLWithString:urlStr];

        NSURL *request = [NSURLRequest requestWithURL:url];

        But it is complaining it here in the, viewDidLoad

        – (void)viewDidLoad
        {
        [super viewDidLoad];
        self.endResultsArray = [[NSArray alloc] init];
        [self displayResults];

        btw here is the displayResults method; it is button.

        -(IBAction)displayResults:(id)sender{

        [self callAPIWithQuery:foodItem.text];
        }

        It will be great if you can help me on this. Let me know if you want more details on this.

        Thanks!

        • Joe Hoffman says:

          Hi Jay,

          I can’t say for sure without the error message, but if I had to guess. I bet it wants you to pass an object into your method call.

          So instead of

          [self displayResults]

          You need to pass an object into it:

          [self displayResults:nameOfObject]

          You can also try passing in nil:

          [self displayResults:nil]

          • This is the exception i am getting;

            2013-05-07 13:19:40.704 Nutritionix1[913:c07] *** Terminating app due to uncaught exception ‘NSInternalInconsistencyException’, reason: ‘-[UITableViewController loadView] loaded the “mGy-QG-Rpt-view-svM-iD-35r” nib but didn’t get a UITableView.’
            *** First throw call stack:
            (0x1cdd012 0x111ae7e 0x1cdcdeb 0x28e357 0x13fff8 0x140232 0x1404da 0x1578e5 0x1579cb 0x157c76 0x157d71 0x15889b 0x1589b9 0x158a45 0x25e20b 0xaf2dd 0x112e6b0 0x22d9fc0 0x22ce33c 0x22d9eaf 0x14e2bd 0x96b56 0x9566f 0x95589 0x947e4 0x9461e 0x953d9 0x982d2 0x14299c 0x8f574 0x8f76f 0x8f905 0x98917 0x5c96c 0x5d94b 0x6ecb5 0x6fbeb 0x61698 0x1c38df9 0x1c38ad0 0x1c52bf5 0x1c52962 0x1c83bb6 0x1c82f44 0x1c82e1b 0x5d17a 0x5effc 0x20fd 0x2025 0x1)
            libc++abi.dylib: terminate called throwing an exception
            (lldb)

          • Joe Hoffman says:

            For those curious about this error, I helped Jay solve this over email. The problem was he was declaring the viewcontroller incorrectly and the compiler was expecting a table view as the main view but he had a table view inside a the main view.

            solved by changing:

            @interface TableViewController : UITableViewController

            to

            @interface TableViewController : UIViewController

            Or by setting the main view to a tableview in the storyboard.

      • Hi do you have a tutorial that shows how to pass data from one view to another?

  5. Barathan K says:

    Great Article and i like the descriptive way of this tutorial a lot. I do have a question, how will you display a another detail table view on clicking of the master tableview row. Please advise.

    • Joe Hoffman says:

      Hi Barathan!

      I think what you are asking is rather than showing some text and an image when a table view cell is clicked, You would rather present this data in another tableview.

      To do this you would need to drag an instance of a UITableView on to the the second view and set up the data source methods to populate these.

      -Joe

  6. Since I’m fairly new to iOS programming, I’ve been executing the tutorial step-by-step and noticed a minor detail that was glossed over. By default, the project initializes on the ViewController instead of the NavigationController. All that needed to be done was to drag the little arrow from the ViewController to the NavigationController.

    Since I haven’t done much iOS programming, this wasn’t obvious to me until I ran the build and noticed that the code in my TableViewController never got called. It took me a few minutes but I eventually figured it out (and learned something!). Maybe you should add this step so that absolute noobs have an easier time :)

    Thanks again!

    Tal

    • Joe Hoffman says:

      Thanks Tal! We’ll get those fixed soon. We’re glad we’re helping you out in your ios endeavors, that’s why we do it!

  7. Two more small notes:

    (1) You forgot to add that the TableViewCell has the UITableViewCellAccessoryDisclosureIndicator accessory type.

    (2) The cell should be of style Subtitle.

    Again thanks for the great work!

  8. Hi,
    I am following your tutorial (thanks a bunch, by the way!) however I am having problems with the following line in the cellForRowAtIndexPath method:

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

    It’s throwing:
    *** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[__NSCFDictionary objectAtIndex:]: unrecognized selector sent to instance 0x884bd10′

    It doesn’t seem to like that I’m attempting to pass an NSArray into the NSDictionary… Am I missing something obvious? I am using a non-google JSON API for my tests ( http://www.reddit.com/.json ). In the request success handler I am doing:

     self.subredditArrayFromAFNetworking = [responseObject objectForKey:@"data"];

    Yet it’s angry. Any ideas?

    • Joe Hoffman says:

      Hi Tom!

      Any Chance you can post the JSON you are trying to read? I suspect the JSON is formatted a bit different than my example.

      • Hi Joe,
        Thanks for the reply!

        The JSON is pretty honkin’ huge, so I’ll just link you to it. It’s just the JSON results for the main page of the site http://www.reddit.com.

        you can access the json here: http://www.reddit.com/.json

        basically, I’m trying to retrieve the “data” for each reddit post. In your example you’re retrieving restaurants, but I’m trying to make a simple Reddit client.

        A small example of the part of the JSON I’m trying to grab is this:

        {

        “kind”: “Listing”,

        “data”: {

        “modhash”: “8j9rm7od6ff2b01795292b3ed1e9605365c46c10b356c66dfa”,

        “children”: [

        {

        “kind”: “t3″,

        “data”: {

        “domain”: “imgur.com”,

        “banned_by”: null,

        “media_embed”: {},

        “subreddit”: “pics”,

        “selftext_html”: null,

        “selftext”: “”,

        “likes”: null,

        “link_flair_text”: null,

        “id”: “1dhovl”,

        “clicked”: false,

        “title”: “Hi from orbit, Reddit! I helped Canada unveil its new high-tech $5 bill yesterday. What do you think of the new design?”,

        “media”: null,

        “score”: 5138,

        “approved_by”: null,

        “over_18″: false,

        “hidden”: false,

        “thumbnail”: “http://d.thumbs.redditmedia.com/Ttefawdd5P6lyRDE.jpg”,

        “subreddit_id”: “t5_2qh0u”,

        “edited”: false,

        “link_flair_css_class”: null,

        “author_flair_css_class”: null,

        “downs”: 13757,

        “saved”: false,

        “is_self”: false,

        “permalink”: “/r/pics/comments/1dhovl/hi_from_orbit_reddit_i_helped_canada_unveil_its/”,

        “name”: “t3_1dhovl”,

        “created”: 1.36745651E9,

        “url”: “http://imgur.com/a/VxMz9″,

        “author_flair_text”: null,

        “author”: “ColChrisHadfield”,

        “created_utc”: 1.36742771E9,

        “distinguished”: null,

        “num_comments”: 1392,

        “num_reports”: null,

        “ups”: 18895

        }

        },
        “kind”: “Listing”,
        “data”: { ….etc…}

        Thanks for any help!

    • Joe Hoffman says:

      Alright Tom, I think I found your problem The structure of this reddit is kind of wanky. But it appears as though your dealing with

      a dictionary within a dictionary within an array within a dictionary within a dictionary ……. so yeah, that took a while to figure out.

      So to start off with:

      self.subRedditArrayFromAFNetworking = [[JSON objectForKey:@"data"] objectForKey:@"children"];

      This chooses the data key and gives another dictionary, then we select the children key which gives us an Array -> Then we can actually perform the next step:

      then your other two lines should look like this:

          NSDictionary *tempDictionary= [self.subRedditArrayFromAFNetworking objectAtIndex:indexPath.row];
           
          cell.textLabel.text = [[tempDictionary objectForKey:@"data"] objectForKey:@"title"];

      This steps through each item of the array and then on each item finds the dictionary key title within the dictionary key data . again

      To track this down, comment all of the code that has to do with the tableview.

      inside the AFNetworking block right after where you load your array with the return value from the request

      put in a code block like:

      NSLog(@"NSLog Printout: %@",[[[[self.subRedditArrayFromAFNetworking objectForKey:@"children"] objectAtIndex:0] objectForKey:@"data"] objectForKey:@"title"]);

      The error you were getting is you were trying to select an index value from a dictionary. You can’t select a numbered object in a dictionary through objectAtIndex, you can only select through a key objectForKey.

      • Oh, this makes a lot more sense now! I knew it was I had the wrong scope (structure seems similar to associative arrays in other languages), I just wasn’t a 100% on how dictionaries and arrays worked in Obj-C (stil new hehe ). Thank you SO MUCH for your help! I really appreciate it :)

        • Joe Hoffman says:

          No problem Tom. I had this issue at first too. It’s a big jump coming from PHP to an objective language.

          • Pedro Ferrer says:

            Hi,
            Im getting an error similar to Tom but this time in the prepareForSegue method.

            THIS IS THE METHOD:
            -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
            {
            NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
            ViewController *detailViewController = (ViewController *)segue.destinationViewController;
            NSLog(@”NSLog Details: %@”,[self.stopsArrayFromAFNetworking objectAtIndex:indexPath.row]);
            detailViewController.stopDetails = [self.stopsArrayFromAFNetworking objectAtIndex:indexPath.row];
            }

            THE CONSOLE OUTPUT:

            2013-05-07 19:23:38.385 test[16194:c07] NSLog Details: {
            address1 = “6259 Beach Blvd”;
            address2 = “”;
            city = Jacksonville;
            code = 627;
            contactEmail = “”;
            contactFax = “”;
            contactName = “Joe Nouripour”;
            contactPhone = “904-805-0055″;
            contactTitle = Owner;
            customerCode = AAA627;
            customerGroupCode = 290;
            customerName = “A & N Auto Care, Inc”;
            destinationCode = CFL;
            isActive = 1;
            lastModifiedBy = “SCS Sync”;
            lastModifiedOn = “2013-05-06 22:10:00″;
            locationTypeCode = “-1″;
            name = “A & N Auto Care”;
            postalCode = 32216;
            state = FL;
            timestamp = 0000000008A5165B;
            }
            2013-05-07 19:23:38.386 test[16194:c07] -[UIViewController setStopDetails:]: unrecognized selector sent to instance 0x7188e80
            2013-05-07 19:23:38.387 test[16194:c07] *** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[UIViewController setStopDetails:]: unrecognized selector sent to instance 0x7188e80′
            *** First throw call stack:
            (0x1db1012 0x1248e7e 0x1e3c4bd 0x1da0bbc 0x1da094e 0x4205 0x5d4b87 0x5d4c14 0x23c249 0x23c4ed 0xc465b3 0x1d70376 0x1d6fe06 0x1d57a82 0x1d56f44 0x1d56e1b 0x21447e3 0x2144668 0x18cffc 0x2d6d 0x2c95)
            libc++abi.dylib: terminate called throwing an exception

            It seems im getting the information OK and in the the cellForRowAtIndexPath its creating the dictionary without a problem so i cant see why in here it wouldnt.

  9. Thanks, it is very helpful.

  10. Gary Makinso says:

    Do you think you can upload the source for this ? I spent more than 6 hours trying to figure it out..

  11. Excellent excellent tutorial, many thanks Joe! You really go through each segment of code explaining as you go along, which I appreciate. I came here from a google search looking how to fetch and display JSON web content, and I must say I am leaving very satisfied! Look forward to more tutorials and will definitely check out the rest. I hope I can ask you a few details for clarification too in the comments should I get stuck :)

    Thanks!

    • Joe Hoffman says:

      HI Ari!

      Thank you for your feedback! These kinds of comments really give us incentive to keep pumping out tutorials. I can’t promise my help will be timely, but I try to respond to questions as quickly as possible.

      -Joe

      • Thanks Joe, much appreciated :)

        When you have time of course, I’ve been trying to modify the code to display JSON that i’ve generated using the json_encode function of PHP.

        The JSON looks like this:

        [{“name”:”Name1″,”address”:”1 Test Ave, Test City”,”phone”:”099-9933303″},{“name”:”Test2″,”address”:”2 Test Ave, Test City”,”phone”:”099-3399393″}]

        Any pointers on how I should edit the above code to store this structure in an array and display? It gives me an error about “objectForKey” having an “unrecognized selector sent to instance 0xyyyyyyy'”. I think that’s because it’s looking for “answer” for example, from the example above, but my generated JSON doesn’t have such a “set”.

        Thanks!

        • sorted it out myself thanks! had to make some adjustments in my php file, used the google JSON output as a reference.

          The key was to match the “set” so that “objectForKey” could pick up each “key” in the JSON. An online JSON validator also helped :)

          Thank you!

          • Joe Hoffman says:

            Good to hear Ari,

            I suspect your error was due to trying to find an item with a key within an array. The compiler in turn complained and said “Hey, Arrays don’t have keys only dictionaries do!”

  12. Thanks for the further clarification Joe!

    I have a quick other question. I’ve tried to modify the project above to push segue from the UITableView to a UITabBarController which replaces the last view controller.

    I’m getting an error with the “-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender” code which says:

    “[UITabBarController setRestuarantDetail:]: unrecognized selector sent to instance 0x8467f80
    *** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[UITabBarController setRestuarantDetail:]: unrecognized selector sent to instance 0x8467f80′ ”

    Now I am assuming this is becuase it is now a UITabBarController and not a UIView. But how would I modify the above prepareForSegue in order to successfully push to the UITabBarController?

    Many Thanks!

    • Joe Hoffman says:

      I’ll take a look at this tonight, but I think all you would do is change the target view inside the prepareforseque method. I don’t have a mac in front of me to verify.

      • I managed to fix this thanks :) I cant remember exactly what I did, but I had to include the header file of the new TabBarControl, and then create a * ‘pointer’ if that’s the right word to this new “RestaurantTabBarController” if that makes any sense.

        Thanks :)

        • Joe Hoffman says:

          Good to hear Ari! So far you’ve been very easy to help out. I bet you even learned more this way too.

          -Joe

  13. great work joe!

    I have a question. I have something that is doing an xml parsing and loading in a table view…more or less the same idea. but my question is that when I have a slow network, it may take a few seconds or longer for the tab to load. I dont want to confuse users, so I would like it to just say Loading data, immediately so the user knows, and then shows the data once the connection is completed….any thoughts?

    thanks again!

    • Joe Hoffman says:

      Hi Joshua!

      I would probably use the activity indicator.

      In the method where you call AFNetworking start an activity indicator animation right before you call AFNetworking. stop indicating right after the AFNetworking call

      I would also add a refresh button there that hides when you press it and gets replaced with the activity indicator.

      You can hide a button by simply adding the code:

      yourButtonOutlet.hidden=TRUE;

      Here’s a link I found for making an activity indicator:

      http://iosdevelopertips.com/user-interface/adding-an-activity-indicator-spinner-to-navigation-bar.html

      I would put the indicator setup code in view did load.

      and then call your AFNetworking in the following order:

      hide button
      start animating

      call AFNetworking

      stop animating
      unhide button

  14. Ravi Vadera says:

    Hi Joe,
    I have a question.. I’m populating my uitableview with data of files and folders that is backed by afnetworking and liferay json web services.. I want that every time user click on row that containss folder it will push the same controller using self segue in storyboard.. All went well but after the segue the data is not populated in next tableview..
    Thanks in advance..

    • Joe Hoffman says:

      Hi Ravi!

      To this date, I haven’t tried segueing to the same view controller. Are you implementing the prepareForSegue method?

      • Ravi Vadera says:

        Sorry for the late reply.. By the way, I have implemented prepareForSegue method but was having same issue. But after a lot time spent on it solved the issue.. I’ll post a snippet so anyone having same issue can solve it.. :)

  15. Hi. First thank you for this and all the other tutorials. It’s great to learn with you. I tried to apply this to a little project I’m building but my JSON is formatted in a completely different way and I wasn’t been able to figure out how to do it. You can see it here https://tennistouch.net/service/RestMatchService.svc/LiveMatches/json
    What I’m trying to get is basically the players ‘name’ and ‘score’. I’d appreciate your help.

    • Joe Hoffman says:

      Hi Dean,

      Let me explain a bit what comes from the AFNetworking piece

      success:^(NSURLRequest *request, NSHTTPURLResponse *response, id responseObject)
                                           {
                                               NSLog(@"JSON RESULT %@", responseObject);

                                           }

      That response object which we later set to the google place array variable can come through as Either a Dictionary or an Array. In your case it looks like it might be a dictionary. which would mean that you’ll have to change your cellForRowAtIndexPath method to accept a dictionary rather than an array.

      The problem I see here is this: It looks like you may only have one game here. which would only be one name and one score. I think if you were to make your API call differently you would get more than one game and that would actually give you the correct format.

      I don’t Have my mac with me now so i can’t provide you some code examples. See what you can find out on your own. If your still having troubles later, I’ll modify post some code here.

      Look above at the solution I sent tom for his reddit client. It may give you some insights.

      • Thank you so much for your answer. I’ll look into it :)

      • Hi. Sorry but I couldn’t figure it out. Spent a few days doodling with it. Since mine is not an Array but a Dictionary inside a Dictionary i get lost somewhere on the way.

        • Joe Hoffman says:

          Hmm,

          After a bit further review, It looks like there is an array her to work with. I found this handy little site which makes your JSON a bit easier to read: json viewer. Either way, it appears by looking at it that you should have an array value for each game. I’ll take a look at it tonight and post the required code.

        • Joe Hoffman says:

          HI Dean,

          Had a chance to look at your problem. It appears as though you have a dictionary within an array.

          So all you should have to do to get the table to populate is change the following bits of code

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

          to

          self.googlePlacesArrayFromAFNetworking = JSON;

          This will get your array if there is a game going on. Then you’ll need to change your dictionary keys so this bit of code changes:

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

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

                  if([tempDictionary objectForKey:@"rating"] != NULL)
                  {
                      cell.detailTextLabel.text = [NSString stringWithFormat:@"Rating: %@ of 5",[tempDictionary   objectForKey:@"rating"]];
                  }  
                  else
                  {
                      cell.detailTextLabel.text = [NSString stringWithFormat:@"Not Rated"];
                  }

              return cell;

          Except you’ll change the String value where it says ObjectForKey to whatever value you want that is shown. I would provide the example but I don’t see any games going on right now.

  16. Hi Joe,

    Thank you for sharing the knowledge! This is an excellent tutorial.

    What do I need to do if I want to populate the UITableView with the files contained in a directory from my server? I also want to show a preview of each document in the DetailViewController.

    Thanks again.

    Cheers,

    • Joe Hoffman says:

      Hi Mike,

      I’m not entirely sure how you would go about doing this, but your server would need some kind of code that can read and provide a list of your files and their locations and send an http response in either XML or JSON. In other words, you’ll have to implement a restful API on your server to serve up the information the app needs about the files.

      Next, to populate the detail view controller you’ll need to implement a document viewer of some sort. I think core image has capabilities to create a view with PDF’s and such. I’d have to look into that piece a bit for you.

  17. Hi Joe, I have a message error when I run the app “Cannot find executable for CFBundle 0xa07a4d0 (not loaded)”

    Thank.

  18. First of All, I’d like to inform you that I’m beginner, I’ve tried many times to run your code
    I’ve Created my Google API key as well, of Places API

    I’m getting error like
    Request Failed with Error: Error Domain=NSURLErrorDomain Code=-1000 “bad URL” UserInfo=0x12165040 {NSUnderlyingError=0x9365e10 “bad URL”, NSLocalizedDescription=bad URL}, {
    NSLocalizedDescription = “bad URL”;
    NSUnderlyingError = “Error Domain=kCFErrorDomainCFNetwork Code=-1000 \”bad URL\” UserInfo=0x9365ea0 {NSLocalizedDescription=bad URL}”;
    }

    Please Help me..
    Thanks in Advanced

    • Joe Hoffman says:

      Hi Vaibhav,

      It looks like your URL is somehow incorrectly formatted. I can’t say for certain without any code to go by. Perhaps you can post the section of code where you set up the URL request? If you do this, you might want to remove your API key.

    • Joe Hoffman says:

      Hi Vaibhav,

      A code error was brought to my attention. I don’t know if you figured this out by now, but the & symbols in the code were replaced with the HTML for &. The http request link has been updated to fix this.

  19. Hi Joe,

    Thanks for this awesome tutorial. I had some issues with the URL from Google Places API. The formatting was incorrect, but eventually figured it out and all is working like a charm. I had no idea the JSON parsing was so easy. Even more so with AFNetworking.

    Some questions and observations:

    1. AFNetworking 2.0 is now out – I noticed that AFJSONRequestOperation no longer exists and has been replaced with AFJSONRequestSererlizer – I had to import the version 1.x for this project as I am not use how to use the new syntax of version 2.0

    2. Would XML Parsing be the exact same? Just pull out the various tags from XML site?
    3. I noticed that in the tutorial you have us update the JSOn URL with this:

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

    This is what gave me issues – the & parts of the URL are incorrect and I did not spot the error for a while. I kept getting denied errors. I then looked at more of your code sample and saw you type the URL like this:

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

    This solved my issues. :)

    I’m going to modify this project to use model objects instead to see how that goes, I think it will be fun. :D

    • Joe Hoffman says:

      H Robert!

      No Problem! Seems like you’re learning a bunch, I like your enthusiasm! Thanks for pointing out those errors, sometimes I get funky issues with HTML that converts things like ampersands incorrectly when I post them. In the near future, I think I’m going to edit this post and bring it up to date with AFNetworking 2.0 and iOS 7, as it seems a lot of people searching iOS programming end up here. Handling XML is nearly identical , you simply replace ‘JSON’ with ‘XML’ in AFNetworking code.

      I have updated the code so those errors should be fixed.

      -Joe

      • Thanks for the reply, Joe.

        I was actually very surprised how quickly I completed this project (Github service issues, today aside) and the reason I looked to this tutorial was that I need to do this for my own app – on a much larger scale. But adapting the project and modifying the code is really how I learn best.

        Yeah the HTML tags did give me issues – but I should have spotted it sooner. I was just a little tired I think. But learnt a lesson to double check URLs correctly. The issue also was that even though the second half od the URL string was incorrect – I still got a success result back with a message saying access denied. So it seemed like the URL was fine. Clearly it wasn’t.

        I would appreciate it if you could find the time to update the post for AFNetwork 2.0. That really would be awesome.

        One last question if you don’t mind:

        With NSURLSession now in iOS7 – is there still a need for AFNetworking wrapper?

        • Joe Hoffman says:

          That IS a good question. It looks like AFNetworking 2.0 has more to offer than NSURLSession alone, but whether or not the overhead is worth it, depends on the application. I suspect Developers will continue to use AFNetworking as it is familiar and simple to use in most cases. I would guess that many developers will us NSURLSession when networking needs are little, and use AFNetworking for heavily networked apps.

          check out this post on NSHipster. http://nshipster.com/afnetworking-2/

  20. Hi Joe,

    Sorry to worry you – but I actually have another question…

    I managed to modify this project to use model objects instead and it worked perfectly and I learnt a lot from dong that.

    However now I am wondering – if AFNetworking can do XML requests – why do developers use NSXMLParser to pull XML data down and what is the difference?

    I think AFNetwork is used for everything related to network connectivity – but would one use NSXMLParser for XML stuff and AFNetworking for other stuff or would you use the two interchangeably?

    Thanks!

  21. I upgraded to AFNetworking 2.0 and used this code to achieve similar results:

    – (void)makeJSONRequest
    {
    AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
    [manager GET:@”https://maps.googleapis.com/maps/api/place/textsearch/json?query=restuarants+in+sydney&sensor=false&key=Your_API_Key” parameters:nil
    success:^(AFHTTPRequestOperation *operation, id responseObject) {
    self.jsonFromAFNetworking = [responseObject objectForKey:@”results”];
    [self.tableView reloadData];
    }
    failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    NSLog(@”Error: %@”, error);
    }];

    }

    Although I did have some trouble getting AFNetworking 2.0 working with my machine through Cocoapods. It seems it won’t recognize the traditional #import “AFNetworking.h” statement. I had to import every module manually in order for it to work so take note if the same happens to you.

    • Joe Hoffman says:

      Thanks for the Heads up Ty!

    • Ali Clarke says:

      I’ve just installed AFNetworking 2.0.3 and i can’t seem to make this work!

      hmm..

      • Ali Clarke says:

        - actually…. spot on! Cheers.. My mistake, i’d forgotten to change the ‘forKey’ to my own value.

        Maybe the author of this blog post could add this to the tutorial? It’d be great for people who are just discovering this!

        Thank you very much +1

  22. Hi Joe,

    Just a quick question if you don’t mind?

    I recently completed another small project, where instead of using AFNetworking to pull XML data, I used NSXMLParser. What I could was that my view is not loaded until there is data to display – this is probably due to the default networking capabilities of SDK and not doing this in the background, like AF does.

    Is there a way to use NSXML + AFnetworking together?

    • Joe Hoffman says:

      Hi Robert,

      Yes, you can mix the two, infact this is exactly how you do it when working with XML. JSON gets parsed for you when using AFNetworking, but XML does not, so in the completion block you need to parse using NSXMLParser.

      I found this article over at raywenderlich.com:
      http://www.raywenderlich.com/30445/afnetworking-crash-course

      While this hasn’t yet been updated for AFNetworking 2.0, it should give you a good idea of what you need to do.

  23. Buenas tardes, disculpa pero soy nueva en el mundo de la programación en xcode, y quisiera saber como traer datos desde una pagina xml a un view controller en mi aplicacion, la aplicacion es una radio que contiene una lista de reproduccion, la cual se trae de la pagina http://www.itculiacan.edu.mx/itcradio/include/horarioapp.php , de antemano te agradesco la ayuda!!

    Translated:
    Good afternoon, sorry but I’m new to the world of programming in xcode, and would like to know how to fetch data from an xml page to a view controller in my application, the application is a radio that contains a list of reproduction, which brings of the page http://www.itculiacan.edu.mx/itcradio/include/horarioapp.php, beforehand I thanks the help!

    • Joe Hoffman says:

      Hi Sara,

      This is nearly the same process except you will need one extra step after the data is retrieved from AFNetworking. In the code, replace JSON with XML. Then use NSXMLParser to translate the data from the data stream to XML. Check out this post by ray: http://www.raywenderlich.com/30445/ . He does a good job of explaining it.

  24. Hello everybody
    Im trying to do this toturial but when I go to google to get the Place API Key it say that I have to enter a webside and I dont have one. Is there a way I can do that without a website?

    • Joe Hoffman says:

      Carlos,

      I don’t think you can, unfortunately :(, try using reddit instead, they don’t currently require any API keys. Read through the comments and you’ll see I’ve instructed someone else on how to do that. If you are looking for places specifically, you could try mapquest or yelp as well.

  25. Ali Clarke says:

    How would i add a ‘swipe to delete’ to this?

    I’ve been trying:

    – (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {

    if (editingStyle == UITableViewCellEditingStyleDelete) {
    //Code here when you hit delete

    // [[self.googlePlacesArrayFromAFNetworking] removeObjectAtIndex:[indexPath.row]];

    [tableView reloadData];
    }

    But i can’t seem to make it work.. any advice?

  26. hello, i was wondering if you could breifly explian how to use user input

    • Joe Hoffman says:

      Hi Jaxson,

      This question isn’t too specific, but I’ll take a crack at what I though you meant. In order to take user input so that they can search for their own cities, you would need to create a text field and take the string that comes from that string and append it to the appropriate fields in the URL. So basically, you want to build the URL using the value that is in the text field.

  27. Thank you so much for this tutorial! =D AFNetworking is great!! I was using NSURLConnection to retrieve data from Google Places, but the table would take like 35 seconds to show. Besides, with AFNetworking you don’t need parsing.
    Thank you AFNetworking.

  28. Bhasin naik says:

    Hi Joe,

    Really nice tutorial. I have implemented your code in xcode 5 on iOS7 framework. When i run the application it shows me blank white screen. Can you help me with this?

    Thanks
    Bhasin

    • Joe Hoffman says:

      Hi Bhasin,

      My first guess is you didn’t connect the delegate and datasource to the table view. It may be possible you also forgot to reload the table in your AFNetworking success block.

      • BHASIN NAIK says:

        Hi Joe
        I also have the same problem as tal.
        My treeviewcontroller doesn’t get called
        Can you be a little specific(if possible please attach the screenshot)?

        Thanks

  29. Troy Bounds says:

    Thanks for saving me from screaming at the screen. Tip came just in time/ Thanks again

  30. Hi Joe,

    I think I want your explanation about these lines of codes
    AFJSONRequestOperation *operation = [AFJSONRequestOperation
    JSONRequestOperationWithRequest:request
    success:^(NSURLRequest *request, NSHTTPURLResponse *response, id responseObject)
    {
    self.googlePlacesArrayFromAFNetworking = [JSON objectForKey:@”results”];
    [self.tableView reloadData];
    }
    as we can see here you change the responseObject to JSON. which I tried to do the same but it didn’t work.

  31. Tharun kumar says:

    I am getting the following error on ViewController.m .. I am trying to use this code for iOS7 (iphone)

    ” ARC Semantic Issue: No visible @interface for ‘UIImageView’ declares the selector ‘setImageWithURL:’ ”

    I followed the steps exactly and not sure where I am going wrong.

    Also, PrepareForSegue method should be written in TableViewController.m??

    Thanks for this recipe. It was really helpful.

  32. yasser says:

    Very awesome tutorial as always Mr Joe :D

  33. Sairan says:

    Спасибо Joe! За поддержку, и добрые советы!

  34. Great article, thanks for the code – saves me typing it all again.

  35. Hello can you add iOS 8 version please ?

Trackbacks

  1. […] Read the original post: iOS Programming Recipe 16: Populating A UITableView With Data … […]

  2. converting tub to walk in shower

    iOS Programming Recipe 16: Populating A UITableView With Data From The Web

Speak Your Mind

*

css.php
Privacy Policy