0

I'm creating deep level tableView (main tableView has two cells, which also have tableViews and they also have other tableViews)

Number of tableViews and cells is limited (it means i don't need recursion)

For rowHeights i'm using UITableView.automaticDimension, but it doesn't works properly.

Here is a screenshot from storyboard:

https://i.stack.imgur.com/3JARb.jpg

And this is a result:

https://i.stack.imgur.com/Kvx9t.jpg

class TipsCountriesTableViewCell: UITableViewCell {

@IBOutlet weak var tipsCountriesTableView: TipsCountriesTableView!
@IBOutlet weak var tipsCountriesHeightConstraint: NSLayoutConstraint!

override func awakeFromNib() {
    tipsCountriesTableView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
    if let obj = object as? UITableView {
        if obj == tipsCountriesTableView && keyPath == "contentSize" {
            if let newSize = change?[NSKeyValueChangeKey.newKey] as? CGSize {
                tipsCountriesHeightConstraint.constant = tipsCountriesTableView.contentSize.height
            }
        }
    }
}

deinit {
    self.tipsCountriesTableView.removeObserver(self, forKeyPath: "contentSize")
}

}

class TipsCitiesTableViewCell: UITableViewCell {

@IBOutlet weak var tipsCitiesTableView: TipsCitiesTableView!
@IBOutlet weak var tipsCitiesHeightConstraint: NSLayoutConstraint!

override func awakeFromNib() {
    tipsCitiesTableView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
    if let obj = object as? UITableView {
        if obj == tipsCitiesTableView && keyPath == "contentSize" {
            if let newSize = change?[NSKeyValueChangeKey.newKey] as? CGSize {
                tipsCitiesHeightConstraint.constant = tipsCitiesTableView.contentSize.height
            }
        }
    }
}

deinit {
    self.tipsCitiesTableView.removeObserver(self, forKeyPath: "contentSize")
}

}

class TipsTableViewCell: UITableViewCell {

@IBOutlet weak var tipsTableView: TipsTableView!
@IBOutlet weak var tipsTableViewHeightConstraint: NSLayoutConstraint!

override func awakeFromNib() {
    tipsTableView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
    if let obj = object as? UITableView {
        if obj == tipsTableView && keyPath == "contentSize" {
            if let newSize = change?[NSKeyValueChangeKey.newKey] as? CGSize {
                tipsTableViewHeightConstraint.constant = tipsTableView.contentSize.height
            }
        }
    }
}

deinit {
    self.tipsTableView.removeObserver(self, forKeyPath: "contentSize")
}

}

class TipsContinentsTableView: UITableView, UITableViewDataSource, UITableViewDelegate {

    var data: [ItineraryTipsContinent]?

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        dataSource = self
        delegate = self
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return data?.count ?? 0
    }

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

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard indexPath.row == 0 else {
            let cell = tableView.dequeueReusableCell(withIdentifier: "TipsCountriesTableViewCell") as! TipsCountriesTableViewCell
            cell.tipsCountriesTableView.data = data?[indexPath.section].countries
            return cell
        }
        let cell = tableView.dequeueReusableCell(withIdentifier: "TipsTableViewCell") as! TipsTableViewCell
        cell.tipsTableView.data = data?[indexPath.section].tips
        return cell
    }

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let sectionHeaderView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "TravelInfoTableViewSectionHeaderView") as! TravelInfoTableViewSectionHeaderView
        guard let data = data else {
            return sectionHeaderView
        }
        sectionHeaderView.cityLabel.text = data[section].name
        sectionHeaderView.arrowImageView.isHidden = false
        return sectionHeaderView
    }
}

class TipsCountriesTableView: UITableView, UITableViewDataSource, UITableViewDelegate {

    private let reuseIdentifier = "TravelInfoTableViewSectionHeaderView"

    var data: [ItineraryTipsCountry]?

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        let nib = UINib(nibName: reuseIdentifier, bundle: nil)
        register(nib, forHeaderFooterViewReuseIdentifier: reuseIdentifier)
        sectionHeaderHeight = 50.0
        rowHeight = UITableView.automaticDimension
        dataSource = self
        delegate = self
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return data?.count ?? 0
    }

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

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard indexPath.row == 0 else {
            let cell = tableView.dequeueReusableCell(withIdentifier: "TipsCitiesTableViewCell") as! TipsCitiesTableViewCell
            cell.tipsCitiesTableView.data = data?[indexPath.section].cities
            return cell
        }
        let cell = tableView.dequeueReusableCell(withIdentifier: "TipsTableViewCell") as! TipsTableViewCell
        cell.tipsTableView.data = data?[indexPath.section].tips
        return cell
    }

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let sectionHeaderView = tableView.dequeueReusableHeaderFooterView(withIdentifier: reuseIdentifier) as! TravelInfoTableViewSectionHeaderView
        guard let data = data else {
            return sectionHeaderView
        }
        sectionHeaderView.cityLabel.text = data[section].name
        sectionHeaderView.arrowImageView.isHidden = false
        return sectionHeaderView
    }
}

class TipsCitiesTableView: UITableView, UITableViewDataSource, UITableViewDelegate {

    private let reuseIdentifier = "TravelInfoTableViewSectionHeaderView"

    var data: [ItineraryTipsCity]?

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        let nib = UINib(nibName: reuseIdentifier, bundle: nil)
        register(nib, forHeaderFooterViewReuseIdentifier: reuseIdentifier)
        sectionHeaderHeight = 50.0
        rowHeight = UITableView.automaticDimension
        dataSource = self
        delegate = self
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return data?.count ?? 0
    }

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

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "TipsTableViewCell") as! TipsTableViewCell
        cell.tipsTableView.data = data?[indexPath.section].tips
        return cell
    }

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let sectionHeaderView = tableView.dequeueReusableHeaderFooterView(withIdentifier: reuseIdentifier) as! TravelInfoTableViewSectionHeaderView
        guard let data = data else {
            return sectionHeaderView
        }
        sectionHeaderView.cityLabel.text = data[section].name
        sectionHeaderView.arrowImageView.isHidden = false
        return sectionHeaderView
    }
}

class TipsTableView: UITableView, UITableViewDataSource, UITableViewDelegate {

    private let reuseIdentifier = "TravelInfoTableViewSectionHeaderView"

    var data: [ItineraryTip]?

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        let nib = UINib(nibName: reuseIdentifier, bundle: nil)
        register(nib, forHeaderFooterViewReuseIdentifier: reuseIdentifier)
        rowHeight = UITableView.automaticDimension
        dataSource = self
        delegate = self
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return data?.count ?? 0
    }

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

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
        cell.textLabel?.text = data?[indexPath.row].text
        cell.textLabel?.font = UIFont.poppinsFont(ofSize: 12.0)
        cell.textLabel?.numberOfLines = 0
        return cell
    }

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let sectionHeaderView = tableView.dequeueReusableHeaderFooterView(withIdentifier: reuseIdentifier) as! TravelInfoTableViewSectionHeaderView
        guard let data = data else {
            return sectionHeaderView
        }
        sectionHeaderView.cityLabel.alpha = 0.48
        sectionHeaderView.cityLabel.font = UIFont.poppinsFont(ofSize: 13.0)
        sectionHeaderView.cityLabel.text = data[section].title
        sectionHeaderView.arrowImageView.isHidden = false
        return sectionHeaderView
    }
}
  • 1
    Possible duplicate of [Self sizing tableview inside self sizing tableview cell](https://stackoverflow.com/questions/57216791/self-sizing-tableview-inside-self-sizing-tableview-cell) – えるまる Sep 10 '19 at 09:05

2 Answers2

0

You have to set constant height to inner UITableView. Add observer for "contentSize" of tableView. You will notified as content size is changed. So you can set tipsCountriesHeightConstraint here.

Add observer in awakeFromNib of TipsCountriesTableViewCell class as below,

tipsCountriesTableView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)

Observe value as below,

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if let obj = object as? UITableView {
        if obj == self.tipsCountriesTableView && keyPath == "contentSize" {
            if let newSize = change?[NSKeyValueChangeKey.newKey] as? CGSize {
                tipsCountriesHeightConstraint.constant = tipsCountriesTableView.contentSize.height
            }
        }
    }
}

Remove observer in deinit

deinit {
    self.tipsCountriesTableView.removeObserver(self, forKeyPath: "contentSize")
}

You must have top, bottom constraints for TipsCountriesTableViewCell items.

Komal Goyani
  • 779
  • 6
  • 25
-1

You need to calculate the size of top level UItableView cell heights by something like this,

            var size = CGSize()
            let cell = YourUITableViewCell()
            cell.textLabel?.text = data?[indexPath.row].text
            let fitting = CGSize(width: cell.frame.size.width, height: 1)
            size = cell.contentView.systemLayoutSizeFitting(fitting,
                                                            withHorizontalFittingPriority: .required,
                                                            verticalFittingPriority: UILayoutPriority(1))
            return size.height

If UILabel is custom then you need to set the bottom Constraint of UILabel to view with less than 1000 priority.

adeel noor
  • 43
  • 1
  • 6