iOS Programming Recipe 11: Using The UITableView Part I

In most apps you’re sure to find a table view whenever a list of items needs to be presented. UITableView, similar to UIPickerView, declares both a data source and delegate protocol. The data source protocol is required while the delegate is optional. We’ll need to make use of the data source protocol for the UITableView class. For this recipe I chose to show a table view with bugs and animals shown. The bugs and animals will have seperate sections.

Assumptions

  • You are familiar with Xcode, if not familiarize yourself here.

Setting Up the View

First start with a single view controller project. Now drag a UITableView object onto the existing view from the object library and then drag a UITableViewCell onto the newly created table view. When you’re done you should have something that looks like this:

First Look

Select the prototype cell and give it an identifier and change the style to “basic” in the properties inspector on the top right:

Creating an Identifier

Now we’ll need some data to add to the table view so create two new arrays. One array we’ll call “bugs” and the other “animals”. Don’t ask why I chose these, I’m not sure either…

Start by modifying the ViewController.h file to add those arrays:


@property (strong, nonatomic) NSArray *bugs;
@property (strong, nonatomic) NSArray *animals;

Then modify the ViewDidLoad method in the ViewContrller.m file as follows:


- (void)viewDidLoad
{
    [super viewDidLoad];
    self.bugs = @[@"Spider",@"Ladybug",@"Firefly"];
    self.animals = @[@"Cat",@"Dog",@"Bigfoot"];
}

Feel free to add as many bugs and animals as you want.

Since we’ll be using the UITableView we’ll need to let the class know we’re going to be implementing the UITableView protocol. Modify the ViewController.h interface line as follows:


@interface ViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>

Now were ready to set up the data source for the UITableView.

Setting Up the Data Source

If you’ve looked at the UIPickerView Tutorial I previously did, this process should look pretty familiar. First we set up the the protocol, then we set up the datasource and delegate methods if applicable. By control clicking the the protocol definition like so:

Getting the UITableView Class

 

This shortcut will take you to the header file that defines the protocol, and shows which methods are required or optional. Once you have control clicked “UITableViewDataSource” you’ll see a protocol definition that looks like this:


@protocol UITableViewDataSource

@required

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

// Row display. Implementers should *always* try to reuse cells by setting each     cell's reuseIdentifier and querying for available reusable cells with   dequeueReusableCellWithIdentifier:
// Cell gets various attributes set automatically based on table (separators)   and data source (accessory views, editing controls)

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

@optional

- (NSInteger)numberOfSectionsInTableView:(UITableView   *)tableView;              // Default is 1 if not implemented

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:   (NSInteger)section;    // fixed font style. use custom view (UILabel) if you    want something different
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:   (NSInteger)section;

// Editing

// Individual rows can opt out of having the -editing property set for them. If     not implemented, all rows are assumed to be editable.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath   *)indexPath;

// Moving/reordering

// Allows the reorder accessory view to optionally be shown for a particular    row. By default, the reorder control will be shown only if the datasource   implements -tableView:moveRowAtIndexPath:toIndexPath:
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath   *)indexPath;

// Index

- (NSArray *)sectionIndexTitlesForTableView:(UITableView    *)tableView;                                                    // return list  of section titles to display in section index view (e.g. "ABCD...Z#")
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:    (NSString *)title atIndex:(NSInteger)index;  // tell table which section    corresponds to section title/index (e.g. "B",1))

// Data manipulation - insert and delete support

// After a row has the minus or plus button invoked (based on the   UITableViewCellEditingStyle for the cell), the dataSource must commit the   change
- (void)tableView:(UITableView *)tableView commitEditingStyle:  (UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath    *)indexPath;

// Data manipulation - reorder / moving support

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath  *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath;

@end

So this piece of code is found in the UITableView class file and it specifically defines the protocol. Here it’s telling us that we need to include the two data source methods numberForRowsInSection and cellForRowAtIndexPath. We’ll also use the optional numberOfSectionsInTableView and titleForHeaderInSection. So go ahead and copy and paste each of these methods into the ViewController.m file and replace the semicolon with open and close brackets. Your .m file should now look something like the following:


- (void)viewDidLoad
{
    [super viewDidLoad];
    self.bugs = @[@"Spider",@"Ladybug",@"Firefly"];
    self.animals = @[@"Cat",@"Dog",@"Bigfoot"];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:  (NSInteger)section
{

}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{

}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:   (NSInteger)section
{

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

}

@end

At this point you’ll see a bunch of warnings. Don’t worry about that, they’re just telling you that you haven’t returned any values from these methods yet. So lets start with the numberOfRowsInSection method. we’ll need to implement a case statement here so the table view will know how many rows to make for each section.


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

   switch (section){
       case 0:
           return [self.bugs count];
           break;
        case 1:
            return [self.animals count];
            break;
        default:
           return 0;
   }

}

Note: I’m showing the section values here to be more instructive, but for the sake of better code (i.e. we want the next guy afer us to be able to understand what the case 0 and 1 mean). They mean the index 0 and 1 of the section, in our case the 0 index means the bugs section and 1 means the animals section. So why don’t we just define it this way in the top of the .m file under the import? While were at it , lets also give a discriptor for the number of sections:


#import "ViewController.h"
#define bugsSection 0
#define animalsSection 1
#define numberOfSections 2

Now let’s refactor the last method a bit


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

    switch (section){
        case bugsSection:
            return [self.bugs count];
            break;
        case animalsSection:
            return [self.animals count];
            break;
        default:
            return 0;
    }

}

That’s better! Next we’ll want to update the amount of sections our table view will have using the numberOfSectionInTableView method (this one is easys!):


- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
   return numberOfSections;
}

The section title header will be updated using the titleForHeaderInSection method. This is similar to the numberOfRowsInSection we already did:


- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
    switch (section){
        case bugsSection:
            return @"Bugs";
            break;
       case animalsSection:
           return @"Animals";
           break;
       default:
           return 0;
   }
}

Alright! The last data source method is a bit more involved. I’ll go ahead and just give the code here and explain it.


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:  (NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"creatures"];

    switch (indexPath.section)
    {

                case bugsSection:
                    cell.textLabel.text = [self.bugs    objectAtIndex:indexPath.row];
                    break;
                case animalsSection:
                cell.textLabel.text = [self.animals     objectAtIndex:indexPath.row];
                    break;
                    default:
                cell.textLabel.text = @"Not Found";

    }

    return cell;
}

The first line asks the table view to dequeue a cell with the “creatures” identifier, if one does not exist in the table view’s reuse pool it is created. It is worth noting, this behavior happens for free in this case because the cell is just a plain UITableViewCell. If the cell had been a UITableViewCell subclass and not defined in a storyboard, it would have been necessary to first register this cell with the table view for reuse in order to get this behavior.

The next part of the method is a switch statement that first checks to see what section is in question and then sets the rows of that section to each item in the array for that section.

Now if you try to run it the table will show up empty. We need to do one last thing. We need to connect the table view’s data source to the view controller from the storyboard so the table view knows where to find these methods. Do this by control dragging from the connections inspector on the top right to the view with the table view selected Like so:

Connecting the Data Source

 

Now the data source is connected and everything should work fine. In Part II of this posting we’ll do something with the delegate and do a bit with customization of the cells. Here’s what you’re final product should look like.

Final Product

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,

    This doesnt work anymore with the new Xcode 4.6 that Im using…
    I have copied and pasted all of your code but still I get to warnings that say

    “Unused variable “bugs”
    “Unused variable “animals”

    in my viewdidload method.. any ideas?

    thanks

  2. Hey Alex!

    Thanks for catching this! It’s not that it doesn’t work anymore, it probably never did work correctly.

    See the problem here is we defined bugs and animals incorrectly.

    Change NSArray *bugs to self.bugs and change NSArrray *animals to self.animals.

    By using NSArray convention we defined it as a local pointer, so bugs and animals cannot be seen outside of the viewdidload function. So as far as viewdidload method is concerned, you never used either pointer. I’ll go ahead and update this, let me know if that fixed it!

    Thanks for reading!

  3. Joe, it got rid of the warnings. So now Ill move on to part two… Looking forward to the tutorial about how to populate a “DetailedView”. It would be awesome if you could make a tutorial on how to populate the “DetailedView” with data retrieved from a plist.. 🙂

    Thanks for posting 😉

    • Thanks Alex!

      I’ll look into it. For future reference, if you have something you would like us to write about go ahead and leave your request on the topic suggestions page.

  4. it would be much better if you upload xCode project file.

    • Joe Hoffman says:

      Hi Ann,

      We didn’t start posting the projects until recently. Unfortunately I did modify the UITableView Part I tutorial to create Part II. I can go ahead and post Part II up on github later this evening. All of our future Recipes will have the source code there.

  5. Sergio Varela says:

    Hi! what if what I got is a project with a main tab bar controller and several view controllers that segue from the main tab bar controller? is it still possible to follow this scheme? thanx i.a.

    • Joe Hoffman says:

      Are you asking whether or not you can segue from a uitabbarcontroller to the uitableviewcontroller?

      • Sergio Varela says:

        Hi again! I tried applying this approach to my View Controller 1 that segues from a main tab Bar Controller, but no luck, instead of a single View application (like the one you use), I have a main Tab Bar Controller which gives me 3 tabs to use, 1, 2 and 3, so I can touch any of them to access 3 different views with 3 different contents, each one created using three different View Controller objects, in View Controller 1 I need to insert a Table View and a Table View Cell, in order to create 3 tabs that would lead me to 3 different subcontents, I applied your approach (actually verbatim) and it goes Ok, no warnings and no issues, but when running in the simulator, what I get is “-[UIView TableView:numberOfRowsInSection:]: unrecognized selector sent to instance 0x755b830” and “*** Terminating app due to uncaught exception ‘NSInvalidArgumentException’ reason: ‘-[UIView TableView:numberOfRowsInSection:]: unrecognized selector sent to instance 0x755b830′”, so I wonder what approach should I follow when instead of a single View application I have a Tabbed application? thanx i.a.

      • Sergio Varela says:

        Hey! good news!, it seemed that I just needed to tell you about it, I can display the tabs as intended at the end of Part 1, I´ll keep you posted as I go on, thanx!!

      • Sergio Varela says:

        Ok, this is the problem, when I do NOT connect the dataSource to the First View Cotroller (the name of my View Controller 1), it goes Ok, I can see the cells but no cell titles, when I connect the dataSource to the View Controller 1, I get the exceptions! so, my question prevails, what do I need to do when instead of a single view application, I do have a tabbed application (through the use of a Tab Bar Controller). Thanx!

        • Joe Hoffman says:

          Sergio , Why don’t I email you directly and you can send me your zipped project folder. I’ll take a peek and let you know what’s up.

          • Joe Hoffman says:

            Sergio,

            Found your problem, select your cell and go to the attributes inspector. Notice that your identifier for the cell is “temas” instead of “creatures”. Either change your identifier to creatures, or modify this line of code:

                UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"creatures"]; //replace creatures with "temas"
  6. Sergio Varela says:

    Thanks so much Joe!! I was so close…. :S thanx for your support and your site, is a great place to learn!

  7. Hi Joe,

    Thanks for these excellent tutorials. They are a great bag of tricks to my development travels.

    I’m almost done with this tutorial, however when I connect the dataSource to the TableView controller – I get this error:

    Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[UIView tableView:numberOfRowsInSection:]: unrecognized selector sent to instance 0xa1ccc60’

    I have checked everything and even just copied your code from this page and still get this error. Any ideas?

    Thanks!

    • Joe Hoffman says:

      Hi Robert,

      Usually this error happens when the number of rows don’t match the number of data items you are trying to put inside of a row. In other words, if the array you are using to populate a row is 5 items, and you are only returning 4 rows you’ll get an error. Hope this helps!

      • Hi Joe,

        I managed to fix it – but I don’t think it was the correct way?

        What I did was create an IBOutlet to the tableView and then set the class as a data source using: self.tableView.dataScource = self.

        Then I removed the connection in the connections inspector.

        This solved the issue for me – but why did I have to do this?

        Thanks
        P.S: Love this site and going through a lot of your tutorials!

        • Hi Robert, I fixed this by connecting CTRL+dragging the dataSource to the File’s Owner instead of the view like the tutorial says. This fixed the problem for me. Your way works as well (and it makes sense why if you think about it).

  8. Hey Jo,
    Your tutorials are really good.

    Here I tried everything exactly as you instructed. Note: I am using an existing viewcontroller I created earlier. Just added new file (Storyboard).

    The error I am getting is: Undeclared identifier tableview and Undeclared identifier section

    Thanks

  9. Hi,
    Thank You for your excellent tutorials. However, when I connect with datasource and delegate, it shows the following error message.” return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));” It would be grateful if you can help me to solve the problem.

    Thank You and Merry Christmas

    • Joe Hoffman says:

      Hi Andy,

      Check and make sure you connected them to the view controller and not another element inside the view controller. Also, add an exception breakpoint for all exceptions so you can see which line in the code is causing the error.

  10. I have one “simple” question, its just to understand how the xcode project works. following example: if i follow the mentioned steps, everything works fine. what i made for tests was, removing the viewcontroller and and a new one. after this, i made the same connections in interface builder as in tutorial (datasource,delegate) but what i get is following error message “unrecognized selector sent to instance”
    ….

    2014-02-05 09:45:48.896
    ViewControllerTest[32618:70b] -[UIViewController
    tableView:numberOfRowsInSection:]: unrecognized selector sent to instance
    0x8a575c0

    2014-02-05 09:45:48.899
    ViewControllerTest[32618:70b] *** Terminating app due to uncaught exception
    ‘NSInvalidArgumentException’, reason: ‘-[UIViewController
    tableView:numberOfRowsInSection:]: unrecognized selector sent to instance
    0x8a575c0’

    Only if i create a new project the error will dissapear, but i’m sure the problem can be solved. for me it looks like, the connection between the controller and the code do not work.
    Any hints? Thanks in advance

    • Joe Hoffman says:

      Hi Peter.

      At first glance it looks like you are trying to send delegate and data source methods to a UIViewController Class. A UIViewController class has no idea what these methods are because it does not conform to a UITableView delegate and protocol. You would need to add the protocol definition for a UITableview in the header. Alternatively, You could create a new class and subclass it with a UITableViewController class.

  11. if you make a tutorial like why not post the code too? I don’t want to spend an hour copying it to see it work..

  12. Ankur Khanna says:

    Hello Joe,

    Loved your tutorial , I am a new to this iOS field so my question could be trivial but would love if you could help me out.

    In the below method on your post

    – (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
    {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@”creatures”];
    ………………

    The cell is never allocated and initialised, so if a cell is nil and I just use the method the cell will always return a nil and there would be a crash, so I guess it should be there ?

    Thanks in advance.

    • Joe Hoffman says:

      Hey Ankur!

      Sorry for the delayed reply. Yes you should allocated and initialize your cell, this article is a bit old, but I think that has changed from iOS 6. At one point you didn’t have to because the storyboard was doing it for you, but I think Apple has rethought this philosophy.

  13. Jesse J. Anderson says:

    I ran in to a few errors with this at the end of the tutorial (I’m using XCode 5 and iOS 7), here’s how I fixed it.

    1. For the dataSource connection in your storyboard, Control-Click and drag from the Table View (check the sidebar to make sure you have the Table View selected, and not the View or the Table View Cell) to the View Controller (either the yellow icon in the bar below your view or again in the sidebar). Select “dataSource” to make this connection.

    If you’ve been messing around with connections in your storyboard, make sure you go through each item in the sidebar to verify that only the correct connections have been made.

    You should have:
    View Controller (Outlets: view connected to View)
    TableView (Outlets: dataSource connected to View Controller)

    and that’s it – all other connections need to be exed out.

    2. This was the big one, could not get the app to run, after much trial and error using NSLogs all over the place to see what was hjappening, I found that “cell” in the cellForRowAtIndexPath method (should be last method in your ViewController.m) was coming up null. to fix I add the following code immediately before the switch statement:

    if (cell == nil) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@”creatures”];
    }

    This code checks to see if there are any cells (there aren’t at the app launch), and if there aren’t it creates one and gives it the default style and (more importantly) gives it the reuseIdentifier of @”creatures”.

    Hope this helps everyone, gave me a huge headache since I’m a newb (started learning a month ago) but happy to have eventually solved it. Works great for me now.

  14. ibuystock says:

    Hi Joe, I want to build an app that has multiple UITableView. I have managed to build a regular table view and pass the segue to another view. But my question is – how to load the second UITABLE and remove the first one?
    It keeps crashing on me with error “libc++abi.dylib: terminating with uncaught exception of type NSException
    (lldb) ” Can you please help?
    THanks,

    • Joe Hoffman says:

      Ok, This is pretty straightforward. You will want to create 3 separate View Controller classes. One for each table view. You can either use a TableViewController class , or make a view Controller class and add a table view like shown in this tutorial. Either way, you will want to set the delegate and datasource in each class. I would also recommend embedding the first view controller in a navigation controller. And I’m assuming clicking a cell in one table view will lead to the next and so on. Embedding in a navigation controller will give you the automatic back functionality. Then you can just segue from one cell to the next. THere’s not enough info on your error to know what exactly went wrong, but usually what happens is A. You forgot to setup Delegate and Datasources, or you accidentally tied them to the view instead of the view controller. B. The numberOfRows doesn’t match the amount of objects you try to make in the cellForRowAtIndexPath method. C. sometime you create an outlet and then delete it from your class but there is still a connection, check the connections inspector.

  15. Hey Joe,

    Great tutorial once again! 🙂

    Say I wanted to get my data that would fill the tableview from another source like a JSON instead of having it directly in the viewDidLoad method like how you have implemented it:
    self.bugs = @[@”Spider”,@”Ladybug”,@”Firefly”];
    self.animals = @[@”Cat”,@”Dog”,@”Bigfoot”];

    How would I go about doing this?
    I’ve already gone through iOS Programming Recipe 16: Populating A UITableView With Data From The Web and am basically adding on to that.
    The JSON I’m using looks something like this:
    {
    storeName = Macy’s;
    storeType = CLOTHING;
    storeId = 19305;
    storeLocation = NY;
    },
    {
    storeName = Nordstrom;
    storeType = CLOTHING;
    storeId = 92817;
    storeLocation = SC;
    },
    {
    storeName = Marshall’s;
    storeType = CLOTHING;
    storeId = 17829;
    storeLocation = AZ;
    },
    {
    storeName = Ashley;
    storeType = FURNITURE;
    storeId = 38201;
    storeLocation = NJ;
    },
    {
    storeName = DSW;
    storeType = SHOES;
    storeId = 10385;
    storeLocation = AL;
    }

    I wanted to separate the table into 3 sections, having
    storeType=CLOTHING in the first section,
    storeType=FURNITURE in the second, and
    storeType=SHOES in the third

    I’m assuming there is a way to use this attribute to separate the data into the different sections but couldn’t figure out how. So far, I created section headers by:

    – (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
    if (section == 0) {
    return @“Clothing”;
    }
    else if (section == 1) {
    return @“Furniture”;
    }
    else {
    return @“Shoes”;
    }
    }
    and can display all 5 stores in each of the sections (so there’s 3 sections and 15 rows) instead of only Clothing stores in the clothing section, furniture stores in the furniture section, etc (i only want 5 rows— 3 rows in the first section, 1 in the second, and 1 in the third).

    This post got super lengthy, sorry! Any help would be appreciated! Thanks!

    Nikki

Speak Your Mind

*

css.php
Privacy Policy