UITableView: Display and hide cells as a dropdown list

When creating iOS applications, the need to pick a value from a list while displaying an UITableView comes up quite often. One obvious solution to that matter, and the hard one in most cases, is to load another UIView which may contain an UIPickerView or an UITableView that will list the values you need, let the user pick the value and then get back into your table and set the picked (by the user) value.

Another solution, more attractive and probably easier for the user is to display the list you need inside the UITableView that's been shown. Perhaps that approach sounds more difficult, but once you try it you'll find out yourself how fast, easy and beautiful solution is. Because an image equals to a thousand  words, the following image might give an idea of what I mean:




Implementing a drop down list inside an UITableView is not hard and it complies with the way that an UITableView works. For this example we'll create a new project, named TblCellsTestApp (you can name it anything you like, I' ll give it that name). Note that I use XCode 4.2 (which I'd suggest you use it as well, if you don't already do), so if you use an older version of the XCode you may notice some differences in the interface and in the view controller names.

Begin a single view application and make sure while creating the project NOT to check the three options at the bottom of the window:


When your project is ready, go to design the view in the one and only XIB file in the Interface Builder. Add an UITableView to the view and for the purposes of this example set its style to "Grouped":



Also, don't forget to make the table delegate and datasource connections:


Before we go any further, let's discuss for a moment what the idea is. To make it more clear we'll base the explanation on this example.

In this project, we're going to have three sections in our UITableView. The first and the third section will just exist in there, we don't care about them and we'll use them just to make our table view more rich. So, we are going to give in both of these sections just one row, with a single, standard text inside them.

What we care about is the second section. While being in normal state, which means that no picking item from a list is needed, we're going to display only one row, containing the selected value by the user, or the pre-selected value by the view when it loads.

On the other hand, while being in picking item state, the number of rows for the specific section that will be displayed will match the number of our data, which will reside into a NSMutableArray array.

The transition between the two states will be taking place as simply as possible. By tapping on the  single row, the section will animate an expand and all the values of our array will be shown, one in each row. By tapping again on any value, the list of the rows will collapse and the picked value will show up in the single row.

We're going to know what our state is at any time, simply by using a Boolean variable, or in other words a flag. Inside our project that flag will take the name "isShowingList". When that flag is True (YES), then all of our demo values/items are being displayed in our section,  one in each row. When that flag is False (NO), then the list is not shown and we're in normal mode.

Let's begin with the implementation.

Go to the view controller's header file (.h) and make it look like the next one:

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController{
    UITableView *table; 
    NSString *dataForSection0;
    NSString *dataForSection2;
    NSMutableArray *demoData;
    int selectedValueIndex;
    bool isShowingList;
}

@property (retain, nonatomic) IBOutlet UITableView *table;
@property (retain, nonatomic) NSString *dataForSection0;
@property (retain, nonatomic) NSString *dataForSection2;
@property (retain, nonatomic) NSMutableArray *demoData;
@property (nonatomic) int selectedValueIndex;
@property (nonatomic) bool isShowingList;

@end


The objects we declared are:
  • table: the UITableView we'll use.
  • dataForSection0: The string value that will be displayed into the row of the first section.
  • dataForSection2: The string value that will be displayed into the row of the third section.
  • demoData: An array that will keep the values/items we need to display or to pick from.
  • selectedValueIndex: An integer value that will show at any time the index of the selected value inside the demoData array.
  • isShowingList: Already mentioned about it. Our state indicator.
Next, go to the .m file and synthesize those objects:

#import "ViewController.h"

@implementation ViewController
@synthesize table;
@synthesize dataForSection0;
@synthesize dataForSection2;
@synthesize demoData;
@synthesize selectedValueIndex;
@synthesize isShowingList;

If you don't want to forget to release the objects, do it now:

- (void)viewDidUnload
{
    [self setTable:nil];
    [self setDataForSection0:nil];
    [self setDataForSection2:nil];
    [self setDemoData:nil];
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

- (void)dealloc {
    [table release];
    [dataForSection0 release];
    [dataForSection2 release];
    [demoData release];
    [super dealloc];
}

Now, that's a good point to go to the Interface Builder and connect the table object with the UITableView we added at the beggining:


Keep coding now. Inside the .m file, in the viewDidLoad method add the following (the comments are quite explanatory):

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    // For the beginning, let's give some values to the strings we declared.
    // Those values will be displayed in the first and the third sections.
    dataForSection0 = @"This is some cell content.";
    dataForSection2 = @"This is another cell content.";
    
    // Let's preprare our actual test data.
    demoData = [[NSMutableArray alloc] init];
    // We'll give five values, from one to five.
    [demoData addObject:@"One"];
    [demoData addObject:@"Two"];
    [demoData addObject:@"Three"];
    [demoData addObject:@"Four"];
    [demoData addObject:@"Five"];
    
    // Initially, the isShowingList value will be set to NO.
    // We don't want the list to be dislplayed when the view loads.
    isShowingList = NO;
    
    // By default, when the view loads, the first value of the five we created
    // above will be set as selected.
    // We'll do that by pointing to the first index of the array.
    // Don't forget that for the five items of the array, the indexes are from
    // zero to four (0 - 4).
    selectedValueIndex = 0;
}

Add some standard UITableView methods:

// Customize the number of sections in the table view.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    // We are going to have only three sections in this example.
    return 3;
}

// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    // The number of the rows is depending on the section index.
    // The sections with indexes 0 and 2 will have only one row.
    if (section == 0 || section == 2) {
        return 1;
    }
    else{
        // For the number of rows of the section with index 1 (our test section) there
        // are two cases.
        //
        // First case: If the isShowingList variable is set to NO, then no list
        // with values should be displayed (the values of the demoData array) and
        // it should exist only one row.
        //
        // Second case: If the isShowingList variable is set to YES, then the
        // demoData array values should be displayed as a list and the returned
        // number of rows should match the number of the items in the array.
        if (!isShowingList) {
            return 1;
        }
        else{
            return [demoData count];
        }
    }
    
}

How about adding some header titles in our sections?

// Add header titles in sections.
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
    if (section == 0) {
        return @"My first section";
    }
    else if (section == 1){
        return @"My demo section";
    }
    else{
        return @"Another section";
    }
}

Let's specify the height of our rows:

// Set the row height.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 60.0;
}

Let's go now to something really important. What the cells are going to display:

// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    static NSString *CellIdentifier = @"Cell";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: CellIdentifier];
    if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    }
    
    // Configure the cell.
    cell.selectionStyle = UITableViewCellSelectionStyleBlue;
    cell.accessoryType = UITableViewCellAccessoryNone;
    
    // Let's set another font for the cells.
    [[cell textLabel] setFont:[UIFont fontWithName:@"Marker Felt" size:16.0]];
    
    // Depending on the section, the appropriate data will be displayed.
    // For the demo section especially, the data display requires a different
    // handling.
    if ([indexPath section] == 0) {
        // We'll set the dataForSection0 string value to the cell of that section.
        [[cell textLabel] setText:dataForSection0];
    }
    else if ([indexPath section] == 2){
        // We'll set the dataForSection2 string value to the cell of that section.
        [[cell textLabel] setText:dataForSection2];
    }
    else{
        // Depending on the isShowingList variable value, we'll display either
        // the selected value of the demoData array only, or the whole array's
        // contents.
        // Remember that if the isShowingList is set to NO, then only a single row
        // is displayed, containing the selected value.
        // If the isShowingList is set to YES, then a list of values is displayed
        // and all the items of the demoData array should be used.
        if (!isShowingList) {
            // Not a list in this case.
            // We'll only display the item of the demoData array of which array 
            // index matches the selectedValueList.
            [[cell textLabel] setText:[demoData objectAtIndex:selectedValueIndex]];
            
            // We'll also display the disclosure indicator to prompt user to
            // tap on that cell.
            cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
        }
        else{
            // Listing the array items.
            [[cell textLabel] setText:[demoData objectAtIndex:[indexPath row]]];
            
            // We'll display the checkmark next to the already selected value.
            // That means that we'll apply the checkmark only to the cell
            // where the [indexPath row] value is equal to selectedValueIndex value.
            if ([indexPath row] == selectedValueIndex) {
                cell.accessoryType = UITableViewCellAccessoryCheckmark;
            }
            else{
                cell.accessoryType = UITableViewCellAccessoryNone;
            }
        }
    }
    
    return cell;
}

Of course, we should't forget what is going to happen when the user taps on a table cell:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    // We don't care about taps on the cells of the section 0 or section 2.
    // We want to handle only the taps on our demo section.
    if ([indexPath section] == 1) {
        // The job we have to do here is pretty easy.
        // 1. If the isShowingList variable is set to YES, then we save the
        //     index of the row that the user tapped on (save it to the selectedValueIndex variable),
        // 2. Change the value of the isShowingList variable.
        // 3. Reload not the whole table but only the section we're working on.
        // 
        // Note that only that last two steps are necessary when the isShowingList
        // variable is set to NO.
        
        // Step 1.
        if (isShowingList) {
            selectedValueIndex = [indexPath row];
        }
        
        // Step 2.
        isShowingList = !isShowingList;
        
        // Step 3. Here I chose to use the fade animation, but you can freely
        // try all of the provided animation styles and select the one it suits
        // you the best.
        [table reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationFade];
    }
    else{
        return;
    }
    
}

Ready to go! Our test app is ready to run now! Test it and see the results.




That's all! I hope that post will help you and you find it really useful.

Happy coding hours!

31 comments:

  1. Great, great tut!

    I do have a question that maybe you could help figure out the logic - I'm sure it has to do with the "selectedValueIndex", which I don't understand why it points to the demoData.

    What I'd like to do is to be able to click on a row in another section, say the dataForSection0, to expand/collapse the demoData section. I'm going to stay on the case as I really need to figure out a solution I've been asked to create, but figured I'd throw it back to you so to hedge my bets.

    Thanks,

    Johnny

    ReplyDelete
    Replies
    1. Dear Johny, first of all thank you for your kind words. I'm really glad if my tutorial helped you even just a bit.

      Let me answer your question now (and see if I understood well). At first, the selectedValueIndex variable exists for two reasons:
      1. When the section 1 is not expanded, it shows the position into the demoData array from which the app should get the text that is displayed on the cell.
      2. When the section 1 is expanded, besides the above, it also shows the index of the cell where the checkmark accessory type should appear.

      In other words, it points to the text that will be displayed on the section1 when it's not expanded and to the cell that should have the checkmark on it when it's expanded.

      Now, to make another section collapsible, you just have to modify the appropriate table view methods. For example, if you want to make the section 0 collapsible, you have to:
      1. Set the correct number of rows for the section 0 inside the - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger) method.
      2. In the "if ([indexpath section] == 0)..." case inside the - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath method specify what is going to be displayed when the section is expanded and when it's not.
      3. If you want to use the demoData array as your data source for both section 0 and section 1, you need to use another flag like the isShowingList that matches to the state of the section 0 (the isShowingList tell us the state for the section 1) and another variable like the selectedValueIndex that will point to the text to be displayed into the demoData array for the section 0.
      4. Of course, you also need to modify the - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath method to respond in your taps at section 0. You just have to do exact the same thing like the example, but for the [indexpath section] == 0 case.

      Generally, you need to have an array as your source for whatever you want to display on the rows of a section, to prepare the mentioned table view methods to work the way you want them to and finally use a flag to know whether the section is expanded or not.

      I hope I covered you somehow...
      Let me know if you need further assistance or some demo code.
      Thanks again.

      Delete
    2. Sorry for the slow follow up, but I've been overwhelmed with projects and this has been my first chance to reply.

      I really appreciate your response - not only fast, but also managing to cover all the bases on such a terse inquiry. In the end, you nailed it in part 4. Aside from look & feel changes I've made to address my specific needs, I NOW have the interaction that'll make my look really, really good (and the time to look at other issues).

      I could not have done this without your assistance; you will get the credit among my peers. Just awesome!

      Thanks for everything,

      Johnny

      Delete
    3. I am really happy that I managed to help you!
      I should thank you for your great words.
      Keep going!

      Delete
  2. Excellent tutorial.. Really helpful.
    I want to ask if I more than 1 row that when selected show more rows then ?

    ReplyDelete
  3. Thats a very helpfull tutorial and easy to understand.
    I spend much time to study it step by step to learn more about tableviews.

    Now i tried to reach the next experience level but i failed until yet.
    Is it possible to create a drop down tableview that also contains sections with
    drop down rows like this example?

    ReplyDelete
    Replies
    1. Well my friend, at first thank you very much for your kind words!
      I'm really happy if I helped you through this guide.

      Now, as far as it concerns your question. To be honest, I've never tried what you say
      (and you just gave me an idea for a new tutorial) but I guess that's possible.
      Making a quick guess, you need to find a way to expand/collapse sections by showing/hiding them
      (for example, when tapping on a button, or on a row of a section that's always visible).

      I'm sorry if I'm not more helpful than that, but as I said I've never tried something like that.
      Given the first opportunity, I'll work with it and I'll prepare a new tutorial to cover that topic too.

      Delete
  4. Thank you for your fast response. Not so bad, i still try by myself.
    I visit your side from time to time and once you have it i will see.

    ReplyDelete
  5. Thank ,It's Really Awesome tute i found here .....

    Thank you vry Much

    ReplyDelete
  6. Great Tutorial! Thank You so much, just what I need now.

    ReplyDelete
  7. I should thank you guys for your great words!
    Keep going!

    ReplyDelete
  8. Great Tutorial ! Are you from np ?

    ReplyDelete
    Replies
    1. Thank you very much and I'm happy I could help you with my tutorial.
      By the way, I'm sorry for the question, but what's np???

      Have a nice day and keep coding...
      Gabriel.

      Delete
  9. Excellent tutorial apply it in some future project...

    ReplyDelete
  10. Thank you so much, very good tutorial

    ReplyDelete
  11. Very very nice
    excellent tutorial
    Saludos

    ReplyDelete
  12. i tried this example but my cell is not droping down can any one help me as soon as possible....

    ReplyDelete
    Replies
    1. Hi my friend. I would recommend to take another look at your steps, in case you missed something. Make sure that you follow the instructions step by step exactly and I'm sure that it will work.
      I wish you good luck.

      Delete
    2. thanx sir it works well........ but i want three dropdown lists i.e on rest of the two cells also....... so can u help me out regarding this issue........

      Delete
  13. I have an existing project that uses the MainStoryboard and would like to use this technique. Can this be modified to be used in a storyboard? Thanks.

    ReplyDelete
  14. Excellent Tutorial. You Rock !!!

    ReplyDelete
  15. Hello,
    I had a question following up the selection of the row from the expanded list, "How would you open a Webpage on the selected row? I tried implementing it, but both expanding/collapsing as well as opening up WebView takes place at the same time. I need to select the specific row first and then open up the webpage.

    Please help me with this, if you have any clue around this.

    Thanks
    iOS Beginner

    ReplyDelete
  16. hi this was good but rahul one question is there how do we connect two drop down feature.for example id if first dropdown we are having countries names.if i select 1 one out it the nest drop down will show its sates if i select different country than the next drop down will show its state..can you please help me buddy.Thanks

    ReplyDelete
    Replies
    1. hi this was good but rahul one question is there how do we connect two drop down feature.for example id if first dropdown we are having countries names.if i select one out it and the next drop down will show its states if i select different country than the next drop down will show its states..can you please help me buddy?.Thanks

      Delete
  17. thanks for great tutorial , in wonder if there's a swift version

    ReplyDelete
    Replies
    1. i'm looking for one too, is there?

      Delete
  18. hi sir thanks for the great tutorial i searche a lot and i found here the detailed solution to add multiple cells in a table view. Now i have a doubt that i have to add image on a particular cell and on selecting that cell it should load the other page consists of details about that particular image and also i need a + buttton in table so that to add images externally to db pls guide me sir

    ReplyDelete
  19. Hi if u have sample code for the same pls forward me on p.saikrishna@outlook.com Thanks in advace.

    ReplyDelete
  20. Hi, nice tutorial but it looks like static cell design... you have not mentioned about tableview content type . plz change the tableview content type to Dynamic Properties or else app is crashing.

    ReplyDelete