Josh Kahane
Josh Kahane

Reputation: 17169

UITableView Mix of Static and Dynamic Cells?

I know you cannot mix Static and Dynamic cell types in a single UITableView but I couldn't think of a better way to describe my issue.

I have several predetermined cells with fixed content, I also have an unknown number of cells with dynamic content which sits in the middle. So I want to my table to look something like this:

Fixed
Fixed
Fixed
Dynamic
Dynamic
Dynamic
Dynamic
Dynamic
Fixed
Fixed

So how exactly do you recommend I approach this in my cellForRowAtIndexPath method?

Thanks.

Upvotes: 45

Views: 32817

Answers (6)

Shan Ye
Shan Ye

Reputation: 2662

After struggled for 1 day, found best solution

Updated for Swift 4

for the case of the question:

  1. Set sections of the tableView to 3

enter image description here

  1. Add a empty tableview cell in the second section which you want to apply the dynamic cells, change identifier of the cell as WiFiTableViewCell

  2. create a new XIB file called WiFiTableViewCell

enter image description here

  1. register the Nib in the function ViewDidLoad in tableViewController

    tableView.register(UINib(nibName: "WiFiTableViewCell", bundle: nil), forCellReuseIdentifier: "WiFiTableViewCell")
    
  2. add the following code to use both dynamic and static cell

    override func numberOfSections(in tableView: UITableView) -> Int 
    {
        return 3
    }
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if section == 1 {
            return self.dataSource.count
            //the datasource of the dynamic section
        }
        return super.tableView(tableView, numberOfRowsInSection: section)
     }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if indexPath.section == 1 {
            let cell = tableView.dequeueReusableCell(withIdentifier: "WiFiTableViewCell") as! WiFiTableViewCell
            return cell
        }
        return super.tableView(tableView, cellForRowAt: indexPath)
    }
    
     override func tableView(_ tableView: UITableView, indentationLevelForRowAt indexPath: NSIndexPath) -> Int {
         if indexPath.section == 1 {
             let newIndexPath = IndexPath(row: 0, section: indexPath.section)
             return super.tableView(tableView, indentationLevelForRowAt: newIndexPath)
         }
         return super.tableView(tableView, indentationLevelForRowAt: indexPath)
     }
    
     override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
         if indexPath.section == 1 {
             return 44
         }
         return super.tableView(tableView, heightForRowAt: indexPath)
      }
    

Upvotes: 34

froggomad
froggomad

Reputation: 1915

  1. Implement your tableView as normal using dynamic prototypes. For each custom cell, use 1 prototype. For an easy example, we'll use 2 cells, AddCell and RoomCell. AddCell is going in the first row, and RoomCell in the 2nd and any cell below.

  2. Remember to increase your count at numberofRowsInSection by the appropriate number of static cells (in this case 1). So if you're returning the count of an array to determine the number of rows, it'd be return array.count + 1 for this example since we have 1 static cell.

  3. I usually create a custom class for each cell as well. This typically makes it simpler to configure cells in the View Controller, and is a good way to separate code, but is optional. The example below is using a custom class for each cell. If not using a custom class, replace the if let statement with an if statement without an optional cast.

note: configureCell below is a method in the custom class RoomCell that dynamically configures the cell

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if indexPath.row == 0 {
        if let cell =  tableView.dequeueReusableCell(withIdentifier: "AddCell", for: indexPath) as? AddCell {
            return cell
        }
    } else {
        if let cell = tableView.dequeueReusableCell(withIdentifier: "RoomCell", for: indexPath) as? RoomCell {
            cell.configureCell(user: user, room: room)
            return cell
        }
    }
    return UITableViewCell() //returns empty cell if something fails
}

Upvotes: 0

Wajih
Wajih

Reputation: 4383

Add a new hidden UITableView holding your Dynamic cells

enter image description here

Steps:

1- In your main UITableViewController append new Section at the end, Inside this Section add only one UITableViewCell, Insert a new UITableView inside the Cell. let us call the new table tblPrototypes because it will be the holder for the Prototype cells.

2- Now set the type of tblPrototypes to Dynamic Prototypes, Then add as many prototype UITableViewCells as you need.

3- Add an Outlet for tblPrototypes in the main controller of the Main Static UITableView, let us call it tablePrototypes and of course it is of type UITableView

Coding part:

First make sure to hide tblPrototypes from the UI, Since it's the last section in your Main Table, you can do this:

@IBOutlet weak var tblPrototypes: UITableView!

override func numberOfSections(in tableView: UITableView) -> Int {
    return super.numberOfSections(in: tableView) - 1
}

The last section will not be presented.

Now when ever you want to display a dynamic cell you do that:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

  let needDynamic = true

  if !needDynamic {
    return super.tableView(tableView, cellForRowAt: indexPath)
  }

  let cellId = "dynamicCellId1"
  let cell = self.tblPrototypes.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
  // configure the cell

  return cell

}

Upvotes: 3

Mazen K
Mazen K

Reputation: 3662

Mixing Dynamic and Static tableviews in Storeboard

suraj k thomas, Have a look on this structure of mine, and works perfectly. @firecast I didn't use constraints within this project, however I did set the height of the container based on the container's tableview height like so:

    ContainerViewController *containerVC = self.childViewControllers.firstObject;
    CGRect frame = self.containerView.frame;
    frame.origin.x = 0;
    frame.origin.y = 0;
    frame.size.width = self.tableView.frame.size.width;
    frame.size.height = containerVC.tableView.contentSize.height;
    self.containerView.frame = frame;
    [self.tableView setTableHeaderView:self.containerView];

Hope this helps, let me know if you have any question.

Upvotes: 11

Mazen K
Mazen K

Reputation: 3662

Best and Easiest Way Yet

1) Place Two Container Views in the Header and in the Footer of a Dynamic TableView 2) Assign the Static TableViews to these Containers and Untick the "Scrolling Enabled"

Please check out my illustration at https://stackoverflow.com/a/22524085/1537178

Upvotes: 20

Mick MacCallum
Mick MacCallum

Reputation: 130193

As you stated you can't mix static and dynamic cells. However, what you can do is break up the content into different data arrays that correspond to each group. Then break the table up into difference sections and load the data from the correct array in cellForRowAtIndexPath:.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *cellID = @"CELLID";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID forIndexPath:indexPath];

    switch (indexPath.section) {
        case 0:{
            cell.textLabel.text = self.arrayOfStaticThings1[indexPath.row];
        }break;

        case 1:{
            cell.textLabel.text = self.arrayOfDynamicThings[indexPath.row];
        }break;

        case 2:{
            cell.textLabel.text = self.arrayOfStaticThings2[indexPath.row];
        }break;

        default:
            break;
    }

    return cell;
}



- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    switch (section) {
        case 0:{
            return self.arrayOfStaticThings1.count;
        }break;

        case 1:{
            return self.arrayOfDynamicThings.count;
        }break;

        case 2:{
            return self.arrayOfStaticThings2.count;
        }break;

        default:
            return 0;
            break;
    }
}

Upvotes: 27

Related Questions