Tableview inside a Tableview cell(using auto-resizing) — Neat and clean!

Kishan Barmawala
3 min readAug 2, 2023
UITableview inside the UITableview cell with dynamic cell height with minimum effort with a neat and clear solution.

While dealing with a UITableview inside UITableViewCell without calculating the nested UITableViewCell content size is such a hectic thing I have ever seen in iOS App development.

I have tried many solutions to achieve this and finally, I have found a lead using a Stackview website’s solution and decided to create this article in brief.

This article will help you to achieve UITableview inside UITableViewCell with the Expandable/Collapsible feature.

To explain this, I have already created an XCode project which is having a UITableView inside another UITableViewCell without a nested UITableView’s height constraint:

This is my ViewController’s hierarchy:

In OuterTableCell, I take a HeaderView, which will hold the School name and add a UITapGestureRecognizer to detect click events on that.

OuterTableCell.swift

override func awakeFromNib() {
addTapEvent()
}

func addTapEvent() {
let panGesture = UITapGestureRecognizer(target: self, action: #selector(handleActon))
headerView.addGestureRecognizer(panGesture)
}

Meanwhile, on click of HeaderView, we will show/hide the InnerTableView.

OuterTableCell.swift

@objc private func handleActon() {
guard let isExpanded = schoolsData?.isExpanded else {
print("isExpanded variable not initialized")
return
}
innerTableView.isHidden = isExpanded
paddingView.isHidden = isExpanded
verticalLineView.isHidden = isExpanded
UIView.animate(withDuration: 0.3) {
self.stackView.setNeedsLayout()
self.helperDelegate?.heightChanged(index: self.index, value: !isExpanded)
}
schoolsData?.isExpanded = !isExpanded
}

Created a HelperDelegate in which I declare heightChanged() function that will call after UIStackView finish resizing the subviews. In addition, I update the required UI changes such as show/hide InnerTableView, VerticalLineView, etc.

OuterTableCell.swift

class OuterTableCell: UITableViewCell {

override func awakeFromNib() {
addTapEvent()
}

func addTapEvent() {
let panGesture = UITapGestureRecognizer(target: self, action: #selector(handleActon))
headerView.addGestureRecognizer(panGesture)
}

@objc private func handleActon() {
guard let isExpanded = schoolsData?.isExpanded else {
print("isExpanded variable not initialized")
return
}
innerTableView.isHidden = isExpanded
paddingView.isHidden = isExpanded
verticalLineView.isHidden = isExpanded
UIView.animate(withDuration: 0.3) {
self.stackView.setNeedsLayout()
self.helperDelegate?.heightChanged(index: self.index, value: !isExpanded)
}
schoolsData?.isExpanded = !isExpanded
}

}

extension OuterTableCell: UITableViewDataSource {

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return schoolsData?.studentsData.count ?? 0
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "InnerTableCell", for: indexPath) as? InnerTableCell else {
return UITableViewCell()
}
cell.studentsData = schoolsData?.studentsData[indexPath.row]
cell.verticalBottomLineView.isHidden = ((schoolsData?.studentsData.count ?? 0)-1) == indexPath.row
return cell
}

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
tableView.invalidateIntrinsicContentSize()
tableView.layoutIfNeeded()
}

}
extension ViewController: UITableViewDataSource {

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return schoolsData.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "OuterTableCell", for: indexPath) as? OuterTableCell else {
return UITableViewCell()
}
cell.schoolsData = schoolsData[indexPath.row]
cell.helperDelegate = self
return cell
}

}

extension ViewController: HelperDelegate {

func heightChanged(index: Int, value: Bool) {
schoolsData[index].isExpanded = value
outerTableView.performBatchUpdates(nil)
}

}

Now you are good to go. You can even explore more by downloading the project.

You can download the source code from here.

I hope it helps! Also, feedback is welcome!

--

--