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.
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];
}
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
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
NSString *identifier = [NSString stringWithFormat:@"%d%d", indexPath.section, indexPath.row]; cell = [tableView dequeueReusableCellWithIdentifier:identifier];
"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.
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.
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()
}
}
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.
#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";
}