45

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.

Josh Kahane
  • 16,765
  • 45
  • 140
  • 253

6 Answers6

33

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)
      }
    
John Pang
  • 2,403
  • 25
  • 25
Shan Ye
  • 2,622
  • 19
  • 21
  • Thanks you @Shan Ye for the great answer. – Parth Dhorda Jul 09 '18 at 10:13
  • This works perfect. By `register` cell, tableView can mix static cell and dynamic cell. – AechoLiu Aug 10 '18 at 07:30
  • STEP 5 is meaningful. It is in order to avoid errors. [More detail can refer this post.](https://stackoverflow.com/a/34019982/419348). And thank @ShanYe again. – AechoLiu Aug 13 '18 at 02:12
  • 2
    Don't forget step 2 like I did (adding an empty cell), this caused a crash that was driving me crazy. – José Nov 03 '18 at 15:01
  • after spending more than 20h you saved me! thanks @Shan Ye – JihadiOS Nov 25 '18 at 03:30
  • This implementation crashed for me. It rendered fine at first, but crashed after updating the datasource and calling `tableView.reloadData()`. I ended up using a solution with table sections. – José Feb 01 '19 at 09:36
  • what do you mean "adding an empty cell"? – coders Feb 22 '19 at 19:18
  • I non so come ringraziarti. Thanks. (Date un nobel a quest'uomo) – Andrea P. Feb 15 '20 at 15:09
  • thank you for posting this solution. it works perfectly. – TSM Mar 16 '20 at 14:22
  • You saved me from crash hell. It's important to override `heightForRowAt:` and `indentationLevelForRowAt:` delegate methods. I lack `indentationLevelForRowAt:` method, and it causes crash when mixing dynamic and static cells. It seems that I encounter the same issue 3 years ago .... haha... – AechoLiu Nov 24 '21 at 09:37
27

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;
    }
}
Mick MacCallum
  • 129,200
  • 40
  • 280
  • 281
20

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

Community
  • 1
  • 1
Mazen Kasser
  • 3,559
  • 2
  • 25
  • 35
  • 1
    @surajkthomas there is no code at all. Storyboard only – Mazen Kasser Mar 14 '14 at 07:31
  • getting this error when i place a container view to footer and added a uitableviewcontroller class to it . Container Views cannot be placed in elements that are repeated at runtime. – Suraj K Thomas Mar 17 '14 at 09:17
  • @surajkthomas Sorry just saw your comment. Check out the screenshot, hope it helps – Mazen Kasser Mar 20 '14 at 05:21
  • 1
    This is the best and easiest solution indeed, creating an embed view controller in storyboard is dead simple and you don't need to figure out the "static cell" offset in tableView:cellForRowAtIndexPath: and tableView:didSelectRowAtIndexPath: – Pride Chung May 07 '14 at 03:32
  • 5
    Or you can put a Dynamic TableView into a Cell of a Static TableView. – whenov Jun 02 '15 at 09:20
  • I tried `Container View` to mix dynamic cell. But it's hard to dynamic height for `Container View`. It seems like the height of `Container View` is fixed. – AechoLiu Aug 10 '18 at 07:31
  • @AechoLiu please check the snippet to bind the height of the container to the tableView content size as shown here https://stackoverflow.com/a/22524085/1537178, hope it helps. – Mazen Kasser Aug 12 '18 at 23:23
  • 1
    @MazenKasser, Thank your reply very much. The tableView's contentSize will change when some checkBox be checked or unchecked, or some UILabel text is multiline. So, it need to sync frame size with contentSize often. By [register a cell](https://stackoverflow.com/a/49157374/419348) for dynamic cells, mix with static tableView cell. It works well, and easy to configure those cells. – AechoLiu Aug 13 '18 at 01:07
11

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.

Mazen Kasser
  • 3,559
  • 2
  • 25
  • 35
  • I had the same problem as the one asked int he structure and implemented your structure to my app. Everything works fine apart from the fact that I'm not able to apply any constraints to the ContainerView using storyboard. Did you try doing that – firecast Oct 20 '15 at 07:01
  • Thanks for the update...I was able to do it programmatically like you have shown above but wanted it using constraints to manipulate height based on the various screen sizes. Thanks anyways :) – firecast Oct 21 '15 at 08:55
  • @firecast you can only implement constraints on a tableview header if you add a view then inside that view add a container, however I believe you still need to programatically set the table header view height from the containerVC.height – Mazen Kasser Oct 05 '16 at 23:52
3

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

}
Wajih
  • 4,227
  • 2
  • 25
  • 40
  • Hey! I did implement your answer. I made 3 sections. 1st is with 4 static cells. 2nd is empty with 0 cells in storyboard. 3rd is hidden with dynamic type cell. I want to populate dynamic cells from 3rd section to 2nd. But after setting number of sections and cells, when I try to set 1st cell to 2nd section - I got 0 index out of empty array range. But, when I change number of cells in 2nd section from storyboard, it works fine. Neither it do with 0 cells. I even check the number of cells in runtime. After setting first dynamic cell to section with 0 cells in storyboard, it crushes. – Oleh Veheria Jan 24 '19 at 10:14
  • 1
    You need to override func `tableView:indentationLevelForRowAt:` to avoid the crash – John Pang Apr 27 '20 at 14:51
0
  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
}
froggomad
  • 1,747
  • 2
  • 17
  • 40