40

I have a static table created in Interface Builder with 6 sections all with different amounts of rows. I now want to add a 7th section with a varying number of rows.

First off, as soon as I uncomment the standard table delegate methods that are inserted by Xcode, I get a crash at self.tableView.tableHeaderView = containerView; where I have added a header to the table.

More importantly i'm getting a crash with the following code

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

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    if (section==6) {
        return 4;
    } else {
        return [super tableView:tableView numberOfRowsInSection:section];
    }
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{/*
    if (indexPath.section == 6) {
        static NSString *CellIdentifier = @"cellWireless";
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

        // Configure the cell...

        return cell;
    }*/
    return [super tableView:tableView cellForRowAtIndexPath:indexPath];
}

How do I correctly leave the existing sections as they are, but add an extra one with a few cells?

Darren
  • 10,182
  • 20
  • 95
  • 162
  • you have to check for number of row in each section. or else you should send the updated data model to your tableview to reload the data with updated data. – Praveen-K Apr 06 '12 at 12:42
  • Does return [super tableView:tableView numberOfRowsInSection:section]; not return the number of rows in each section? – Darren Apr 06 '12 at 12:45
  • Above methods are when you use prototype cells (dequeueReusableCellWithIdentifier). You are using static cells. – Sierra Alpha Apr 06 '12 at 13:34
  • "...take one of two approaches based on whether the cells are for static or dynamic row content. With dynamic content, the table view is a list with a large (and potentially unbounded) number of rows. With static content, the number of rows is a finite, known quantity..." Source: http://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/TableView_iPhone/TableViewCells/TableViewCells.html#//apple_ref/doc/uid/TP40007451-CH7 – Sierra Alpha Apr 06 '12 at 13:48
  • So I can't add dynamic content to a static table? – Darren Apr 06 '12 at 14:08
  • 4
    I have found a solution here https://devforums.apple.com/message/502990#502990 in the last post. you have to override every tableview delegate method. – Darren Apr 06 '12 at 14:50
  • I have the dynamic cells working in the way I can change how many of them there are, however I have another problem getting a reuse cell to work. http://stackoverflow.com/questions/10046870/cant-get-a-static-reusable-cell-to-work – Darren Apr 06 '12 at 17:23

7 Answers7

50

To add dynamic cells to a static cells table you have to override every UITableView delegate method that has an indexPath.

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

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

-(BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath

-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
-(NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

.

-(BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
     return NO;
}

-(BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{     
     return NO;
}

-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{     
     return UITableViewCellEditingStyleNone;     
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
     int section = indexPath.section;

     // if dynamic section make all rows the same height as row 0
     if (section == self.dynamicSection) {
          return [super tableView:tableView heightForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]];
     } else {
          return [super tableView:tableView heightForRowAtIndexPath:indexPath];
     }
}

- (NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath
{
     int section = indexPath.section;

     // if dynamic section make all rows the same indentation level as row 0
     if (section == self.dynamicSection) {
          return [super tableView:tableView indentationLevelForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]];
     } else {
          return [super tableView:tableView indentationLevelForRowAtIndexPath:indexPath];
     }
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
     if (section == self.dynamicSection ) {
          return [self.dataListArray count];
     } else {
          return [super tableView:tableView numberOfRowsInSection:section];
     }
}

-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
     int section = indexPath.section;
     int row = indexPath.row;


     if (section == self.dynamicSection) {
          // make dynamic row's cell
          static NSString *CellIdentifier = @"Dynamic Cell";
          UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

          if (!cell) {
               cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
          }

          cell.textLabel.text = [self.dataListArray objectAtIndex:row];
          return cell;
    } else {
          return [super tableView:tableView cellForRowAtIndexPath:indexPath];
    }
}

Only once you have every method overridden will your table start to work. For any referencing the static section, just refer to [super].

Darren
  • 10,182
  • 20
  • 95
  • 162
  • 3
    Actually, if you want to create as much section as you want dynamically, you need to implement `numberOfSectionsInTableView:` too. And to solve problem of going out of array bounds, you will need to override ALL methods with indexPath! Some are not listed here, like `titleForHeaderInSection` or `heightForHeaderInSection` and `viewForHeaderInSection`. – DanSkeel Aug 14 '12 at 21:39
  • I don't think you need to override all the methods of the static table to make it successful like this answer suggests. I'll provide a separate answer that shows what worked for me. Overriding every method isn't required, but what you override needs to provide a super implementation as well as your custom one. – JasonD Oct 08 '12 at 02:12
  • That works for me, but only partially. Rather than displaying the custom cell, the table shows an empty cell (with correct size, etc). From what I experienced I can tell that `dequeueReusableCellWithIdentifier` always returns `nil` in a static table, thus we're left only with `cellForRowAtIndexPath`, which doesn't help because we want to add more cells to the section, and not change those we already have there. So we end up manually creating new cells with `MyCustomCell *cell = [super.tableView dequeueReusableCellWithIdentifier:@"MyCustomCellId"];`, which also does NOT seem to work. Any clues? – jweyrich Nov 28 '12 at 17:58
  • **[This question](http://stackoverflow.com/questions/9993669/storyboard-static-cells-dequeuereusablecellwithidentifier-returns-nil)** seems related to my previous comment. – jweyrich Nov 28 '12 at 18:11
  • jweyrich, I have the same problem. Do you have any progress? Here my question: http://stackoverflow.com/questions/12858363/uitableview-handle-cell-selection-in-a-mixed-cell-table-view-static-and-dynamic – Ricardo Jan 19 '13 at 12:43
  • @Ricardo [here is my opinion](https://plus.google.com/105134410756311737816/posts/dDvQQX2MEzD) on the subject. – jweyrich Jan 25 '13 at 22:06
  • @Darren, I think it is impossible to get custom dynamic cells working with static cells. Correct me if I am wrong – Mazen Kasser Feb 04 '14 at 00:00
  • @Darren I said CUSTOM CELL. You are using UITableViewCell not custom – Mazen Kasser Apr 04 '14 at 05:07
  • Thanks a lot. I had the problem that my static table view had 3 rows but my dynamic datasource had a variable count. Everything was fine when I had 0 to 3 rows but my app would crash as soon as I had more than 3. Now that I implemented ALL methods, It works with any number of rows – xxtesaxx Jan 11 '17 at 13:11
11

Darren's answer gave me the idea for what worked for me, however I didn't have to go so far as to implement every single tableView delegate method. You really only need to override numberOfRowsInSection and cellForRowAtIndexPath.

First I defined a static table in Interface Builder with 4 sections, 2 to 4 cells per section. I wanted section 0, 2 and 3 to be static and look exactly as they did in IB, but I wanted section 1 to have a custom number of rows with a custom display in each cell based on an array of values I had.

In the view controller for the static table, override the number of cells returned for your dynamic section, but accept the defaults for all other sections (they'll fall back to the IB values). Do the same for cellForRowAtIndexPath and return the [super] implementation for all sections except section 1.

@implementation myMostlyStaticTableViewController
@synthesize myFancyArray;

- (NSInteger) tableView:(UITableView *) tableView numberOfRowsInSection:(NSInteger) section
{
    if (section == 1)
        return [myFancyArray count]; // the number of rows in section 1
    else
        return [super tableView:tableView numberOfRowsInSection:section];
}

- (UITableViewCell *) tableView:(UITableView *) tableView cellForRowAtIndexPath:(NSIndexPath *) indexPath
{
    // for cells not in section 1, rely on the IB definition of the cell
    if (indexPath.section != 1)
        return [super tableView:tableView cellForRowAtIndexPath:indexPath];

    // configure a task status cell for section 1
    MyCustomTableViewCell *cell;
    cell = [tableView dequeueReusableCellWithIdentifier:@"myCustomCell"];
    if (!cell)
    {
        // create a cell
        cell = [[MyCustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"myCustomCell"];
    }
    cell.myCustomLabel.text = [myFancyArray objectAtIndex:indexPath.row];
    return cell;
}
@end

And of course you need a custom cell:

@implementation MyCustomTableViewCell

- (UITableViewCell *) initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    // initialize cell and add observers
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (!self)
        return self;
    self.clipsToBounds = YES;
    self.selectionStyle = UITableViewCellSelectionStyleNone;

    // configure up some interesting display properties inside the cell
    _label = [[UILabel alloc] initWithFrame:CGRectMake(20, 9, 147, 26)];
    _label.font = [UIFont fontWithName:@"HelveticaNeue-Medium" size:17];
    _label.textColor = [UIColor colorWithWhite:0.2 alpha:1];
    [self.contentView addSubview:_label];

    return self;
}

@end
JasonD
  • 7,472
  • 5
  • 31
  • 31
9

I will post answer in Swift, but it should work in Objective-C as well.

In my experience, it was enough to override these methods in UITableViewController:

tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
tableView(tableView: UITableView, indentationLevelForRowAtIndexPath indexPath: NSIndexPath) -> Int

If you want to have custom table view cell in your table view, you need to crate subclass of UITableViewCell also with nib, and register it to your table view.

My whole controller looks like this:

var data = ["Ahoj", "Hola", "Hello"]

override func viewDidLoad() {
    super.viewDidLoad()

    tableView.registerNib(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: "reuseIdentifier")
}

// MARK: - Table view data source

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if section == 1 {
        return data.count
    }
    return super.tableView(tableView, numberOfRowsInSection: section)
}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    if indexPath.section == 1 {
        let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath) as! CustomCell
        cell.titleLabel.text = data[indexPath.row]
        return cell
    }
    return super.tableView(tableView, cellForRowAtIndexPath: indexPath)
}

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return 44
}

override func tableView(tableView: UITableView, indentationLevelForRowAtIndexPath indexPath: NSIndexPath) -> Int {
    return 0
}

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    tableView.deselectRowAtIndexPath(indexPath, animated: true)
    if indexPath.section == 1 {
        print(data[indexPath.row])
    }
}

@IBAction func addItem() {
    data.append("Item \(data.count)")
    tableView.beginUpdates()
    tableView.insertRowsAtIndexPaths([NSIndexPath(forRow: data.count - 1, inSection: 1)], withRowAnimation: .Left)
    tableView.endUpdates()
}

@IBAction func removeItem() {
    if data.count > 0 {
        data.removeLast()
        tableView.beginUpdates()
        tableView.deleteRowsAtIndexPaths([NSIndexPath(forRow: data.count, inSection: 1)], withRowAnimation: .Left)
        tableView.endUpdates()
    }
}
Tejus Prasad
  • 6,322
  • 7
  • 47
  • 75
Gibastek
  • 143
  • 1
  • 8
7

I thought I'd add an updated answer based on @Darren's excellent answer. Most of the delegate methods are not required. So, I just added the required ones. You can easily add a custom cell if you wish, even using a nib file. The image shows a static table with 3 sections. The final section is run time dynamic. This is extremely handy. This is working in ios7 BTW.

enter image description here

#define DYNAMIC_SECTION 2

#import "MyTableViewController.h"

@interface MyTableViewController ()
@property (strong, nonatomic)NSArray *myArray;
@end

@implementation MyTableViewController

- (id)initWithCoder:(NSCoder *)aDecoder
    {
        if (self = [super initWithCoder:aDecoder]) {
            _myArray = @[@"ONE", @"TWO", @"THREE", @"FOUR"];
        }
        return self;
    }

    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
    {
        return [super numberOfSectionsInTableView:tableView];
    }

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
        if (section != DYNAMIC_SECTION) {
            return [super tableView:tableView numberOfRowsInSection:section];
        }
        return [self.myArray count];
    }

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        if (indexPath.section != DYNAMIC_SECTION) {
            return [super tableView:tableView cellForRowAtIndexPath:indexPath];
        }
        static NSString *id = @"MyCell";
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:id];
        if (!cell) {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:id];
        }
        cell.textLabel.text = self.myArray[indexPath.row];
        return cell;
    }

        // required
    -(NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        int section = indexPath.section;
        if (section == DYNAMIC_SECTION) {
            return [super tableView:tableView indentationLevelForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]];
        } else {
            return [super tableView:tableView indentationLevelForRowAtIndexPath:indexPath];
        }
    }

            // Not required
    - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
    {
        if (section != DYNAMIC_SECTION) {
            return [super tableView:tableView titleForHeaderInSection:section];
        }
        return @"some title";
    }
SmileBot
  • 19,393
  • 7
  • 65
  • 62
  • 1
    Thanks for this answer. It's worth noting that if you're using a storyboard then this answer requires that the table view's section 2 should contain at least one cell from which the cell height can be obtained. Otherwise it'll crash with array out of bounds...! – fatuhoku Oct 23 '15 at 13:17
  • 2
    I also found that overriding the tableView:heightForRowAtIndexPath: delegate method was required (under iOS 9 at least!) @smileBot: // required -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { NSInteger section = indexPath.section; if (section == DYNAMIC_SECTION) { return [super tableView:tableView heightForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]]; } else { return [super tableView:tableView heightForRowAtIndexPath:indexPath]; } } – fatuhoku Oct 23 '15 at 13:21
  • 3
    I've created a project that demonstrates this working at: https://github.com/fatuhoku/demo-ios-hybrid-static-and-dynamic-uitableview – fatuhoku Oct 23 '15 at 13:33
0

I think you are going to have to make your UITableView dynamic. Being that you have an "unknown"number of rows, you will most likely set the delegate method to something like this:

 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [someArray count];
}
Johnny Gamez
  • 259
  • 6
  • 19
0

I discovered something pretty interesting I think and it's more worth an answer than a "comment". I had this static tableView with dynamic rows working, and then it stopped working. The reason is simple. I previously had

[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier]

and later decided I wanted/needed a Custom Cell that I'd design in my StoryBoard and only set outlets to my UITableView Subclass. So I used the other technique

[super tableView:tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:indexPath.section]];

The problem here seems to be that this cell gets reused and thus you'll only see one of the cells at a time. Sometime you'll even see none, they'll all be empty! If you scroll you'll see the other cells shortly appearing then disappearing (more like flickering!).

This drove me seriously nuts, until I realized what was (im)possible. Furthermore, do not try to do

[super.tableView dequeueReusableCellWithIdentifier:CellIdentifier]

because as mentioned by other people this always returns nil in a static tableView.

———

So I'm unsure what to do. I guess I'll use the "static prototyped" route, which consists of

  • Using a Prototype Table View with Cell Identifiers like "31" for Section 3 Row 1. I can then do something like
NSString *identifier = [NSString stringWithFormat:@"%d%d", indexPath.section, indexPath.row];
cell = [tableView dequeueReusableCellWithIdentifier:identifier];
  • Use Prototype Cells as well for the Headers. I use "Cell1-Header" for the Cell Identifier of the header of the section 1 and then have something like
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    NSString *identifier = [NSString stringWithFormat:@"Cell%d-Header", section];
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
    return cell.contentView;
}

The basic thing to take here is that you can always starts with a static tableView, but the moment where you realize you're gonna need something dynamic, swap it to Prototype (it will keep your rows although I don't remember what it does with the sections!) and use this KISS technique.

StuFF mc
  • 4,137
  • 2
  • 33
  • 32
0

I think I found a better and easier solution, with "fantom" sections or rows in IB.

In case you know the maximum number of cells you would use in section 7(lets say 10), you should set the number of rows to 10, when you configure section 7 in IB.

You aren't forced to use all 10 rows in section, this can be set by

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

For example if you return 5 when section == 6(actually the 7th section), then only 5 rows will be displayed.

I admit that's not dynamic in the absolute sense of the word, but perhaps resolves most of the cases.

Robert
  • 5,278
  • 43
  • 65
  • 115
lacixp
  • 1