I need to put UITableView inside UITableViewCell with auto-layout because second table has different number of rows and one row can have different height.
This is my ViewController
class ViewController: UIViewController {
let tableView = UITableView()
let cellId = "firstTableCellId"
override func viewDidLoad() {
super.viewDidLoad()
setupView()
tableView.reloadData()
view.backgroundColor = UIColor.gray
}
func setupView() {
tableView.delegate = self
tableView.dataSource = self
tableView.separatorStyle = .none
tableView.register(NextTable.self, forCellReuseIdentifier: cellId)
tableView.backgroundColor = UIColor.green
tableView.separatorStyle = .singleLine
view.addSubview(tableView)
view.addConstraintsWithFormat("V:|-60-[v0]-5-|", views: tableView)
view.addConstraintsWithFormat("H:|-8-[v0]-8-|", views: tableView)
}
}
extension ViewController: UITableViewDelegate {
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! NextTable
cell.layoutIfNeeded()
return cell
}
}
And NextTable which is the cell in first table
class NextTable: UITableViewCell {
var myTableView: UITableView!
let cellId = "nextTableCellId"
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = UIColor.brown
setupView()
myTableView.reloadData()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
let label: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.text = "Next table:"
label.textColor = UIColor.black
label.sizeToFit()
label.backgroundColor = UIColor.cyan
return label
}()
func setupView() {
myTableView = UITableView()
myTableView.delegate = self
myTableView.dataSource = self
myTableView.separatorStyle = .singleLineEtched
myTableView.backgroundColor = UIColor.blue
myTableView.register(TableCell.self, forCellReuseIdentifier: cellId)
myTableView.isScrollEnabled = false
addSubview(myTableView)
addSubview(label)
addConstraintsWithFormat("H:|-30-[v0]-30-|", views: myTableView)
addConstraintsWithFormat("H:|-30-[v0]-30-|", views: label)
addConstraint(NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 15))
addConstraint(NSLayoutConstraint(item: myTableView, attribute: .top, relatedBy: .equal, toItem: label, attribute: .bottom, multiplier: 1.0, constant: 0))
addConstraint(NSLayoutConstraint(item: label, attribute: .bottom, relatedBy: .equal, toItem: myTableView, attribute: .top, multiplier: 1.0, constant: 0))
addConstraint(NSLayoutConstraint(item: myTableView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: -15))
}
}
extension NextTable: UITableViewDelegate {
}
extension NextTable: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 60
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! TableCell
cell.layoutIfNeeded()
return cell
}
}
And cell in second table
class TableCell: UITableViewCell {
let label: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.text = "Some text"
label.textColor = UIColor.black
label.sizeToFit()
label.backgroundColor = UIColor.red
return label
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = UIColor.yellow
setupView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupView(){
addSubview(label)
addConstraintsWithFormat("V:|-8-[v0]-8-|", views: label)
addConstraintsWithFormat("H:|-5-[v0]-5-|", views: label)
}
}
This is the effect of my code
There is no second table so I create new class and use it in cell as new table view
class InnerTableView: UITableView {
override var intrinsicContentSize: CGSize {
return self.contentSize
}
}
And now table is show but size is too large
What can I do to show full second table without empty space at bottom of cell.
I added override for property in my table view:
override var intrinsicContentSize: CGSize {
self.layoutIfNeeded()
return self.contentSize
}
Full code for my table in table can you find on my GitHub
In your first table view, add this to your cellForRowAt
after you dequeue your cell (might need to be slightly tweaked to fit your implementation):
cell.tableView.reloadData()
DispatchQueue.main.async {
cell.tableView.scrollToRow(at: IndexPath(row: cell.numberOfRowsInInnerTableView.count.count - 1, section: 0), at: .bottom, animated: false)
}
DispatchQueue.main.async {
cell.tableView.invalidateIntrinsicContentSize()
cell.tableView.layoutIfNeeded()
self.updateHeight()
}
Then define a function updateHeight
in the same class (outer table view):
func updateHeight() {
UIView.setAnimationsEnabled(false)
tableView.beginUpdates()
tableView.endUpdates()
UIView.setAnimationsEnabled(true)
}
Obviously, this is kind of hacky, but essentially it allows the cell's InnerTableView
to know the actual height of all of the inner cells and resize the outer tableview appropriately. This method worked for me.
I tried the above solution but it haven't worked for me. I have done the small change and then it worked fine for me.
A) In my ViewController:
//NOTE: TestQuestionAnsOptionCell is the outer cell
if let cell = tableView.dequeueReusableCell(withIdentifier: String(describing:TestQuestionAnsOptionCell.self), for: indexPath) as? TestQuestionAnsOptionCell {
cell.reloadData()
setUpInnerCellListner(cell: cell)
cell.selectionStyle = .none
return cell
}
private func setUpInnerCellListner(cell:TestQuestionAnsOptionCell) {
cell.reloadTable = { // Closure called from TestQuestionAnsOptionCell
DispatchQueue.main.async {
UIView.setAnimationsEnabled(false)
self.tbQuestion.beginUpdates()
self.tbQuestion.endUpdates()
UIView.setAnimationsEnabled(true)
}
}
}
Extention used here:
extension NSObject {
class var className: String {
return String(describing: self)
}
}
B) In TestQuestionAnsOptionCell:
class TestQuestionAnsOptionCell: UITableViewCell {
var tblOptions: OwnTableView = OwnTableView()
var reloadTable:(()->(Void))?
override func awakeFromNib() {
super.awakeFromNib()
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
private func setupView() {
tblOptions.estimatedRowHeight = 60.0
tblOptions.rowHeight = UITableView.automaticDimension
tblOptions.delegate = self
tblOptions.dataSource = self
tblOptions.separatorStyle = .none
tblOptions.register(UINib(nibName: TestQuestionOptionCell.className, bundle: nil), forCellReuseIdentifier: TestQuestionOptionCell.className)
tblOptions.isScrollEnabled = false
addSubview(tblOptions)
addConstraintsWithFormat("H:|-30-[v0]-30-|", views: tblOptions)
addConstraint(NSLayoutConstraint(item: tblOptions, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 15))
addConstraint(NSLayoutConstraint(item: tblOptions, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: -15))
}
func reloadData() {
tblOptions.reloadData()
}
}
With TableView Delegate and datasource, I used the following:
extension TestQuestionAnsOptionCell:UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//NOTE: TestQuestionOptionCell is the inner cell
if let cell = tableView.dequeueReusableCell(withIdentifier: TestQuestionOptionCell.className, for: indexPath) as? TestQuestionOptionCell {
cell.updateCell()
cell.selectionStyle = .none
return cell
}
return UITableViewCell()
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
tblOptions.invalidateIntrinsicContentSize()
tblOptions.layoutIfNeeded()
reloadTable?()//Closure to update outer tableview
}
}
C) Inner tableview is used as:
class OwnTableView: UITableView {
override var intrinsicContentSize: CGSize {
self.layoutIfNeeded()
return self.contentSize
}
override var contentSize: CGSize {
didSet{
self.invalidateIntrinsicContentSize()
}
}
}
D) UIView Extention is as:
extension UIView {
func addConstraintsWithFormat(_ format: String, views: UIView...) {
var viewsDictionary = [String:UIView]()
for(index, view) in views.enumerated() {
let key = "v\(index)"
view.translatesAutoresizingMaskIntoConstraints = false
viewsDictionary[key] = view
}
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutConstraint.FormatOptions(), metrics: nil, views: viewsDictionary ))
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With