Self sizing tableview inside self sizing tableview cell
Asked Answered
G

3

0

Let's say I have hierarchy like this:

*TableViewCell
**TableView
***TableViewCell

and all of them should be resizable. Did someone face this kind of problem? In past I've used many workarounds like systemLayoutSizeFitting or precalculation of height in heightForRowAt, but it always breaks some constraints, because TableViewCell has height constraint equal to estimated row height and there appear some kinds of magic behavior. Any ways to make this live?

Current workaround:

class SportCenterReviewsTableCell: UITableViewCell, MVVMView {
    var tableView: SelfSizedTableView = {
        let view = SelfSizedTableView(frame: .zero)
        view.clipsToBounds = true
        view.tableFooterView = UIView()
        view.separatorStyle = .none
        view.isScrollEnabled = false
        view.showsVerticalScrollIndicator = false
        view.estimatedRowHeight = 0
        if #available(iOS 11.0, *) {
            view.contentInsetAdjustmentBehavior = .never
        } else {
            // Fallback on earlier versions
        }

        return view
    }()

    private func markup() {
        contentView.addSubview(tableView)
        tableView.delegate = self
        tableView.dataSource = self
        tableView.register(ReviewsTableViewCell.self, forCellReuseIdentifier: "Cell")
        tableView.snp.makeConstraints() { make in
            make.top.equalTo(seeAllButton.snp.bottom).offset(12)
            make.left.equalTo(contentView.snp.left)
            make.right.equalTo(contentView.snp.right)
            make.bottom.lessThanOrEqualTo(contentView.snp.bottom)
        }
    }


    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ReviewsTableViewCell

        cell.viewModel = viewModel.cellViewModels[indexPath.row]

        return cell
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! ReviewsTableViewCell

        cell.viewModel = viewModel.cellViewModels[indexPath.row]
        cell.setNeedsLayout()
        cell.layoutIfNeeded()
        let size = cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: .defaultHigh, verticalFittingPriority: .defaultLow)

        return size.height
    }
}

Self sizing tableView class:

class SelfSizedTableView: UITableView {
    override func reloadData() {
        super.reloadData()
        self.invalidateIntrinsicContentSize()
        self.layoutIfNeeded()
    }

    override var intrinsicContentSize: CGSize {
        self.setNeedsLayout()
        self.layoutIfNeeded()
        return contentSize
    }
}

Gittel answered 26/7, 2019 at 9:13 Comment(15)
I would try to set automaticDimension for cells, and dynamic height for tableView(height priority = 750, isScrollEnabled = false), or try to set table height to its contentSizePastoralize
@AlexandrKolesnik I've provided more information, please check it out :). Then, contentSize of cells with automaticDimension are equal to estimatedRowHeight, it would be setting tableView height = estimatedRowHeight * numberOfRows.Derek
#48032469Cesium
@PrashantTukadiya sorry, but your solution looks crutch for me. I need something more stable.Derek
@Erumaru Yes I know it is kind of hack, I have tried every possible thing I know to fix this issue, But every solution has some kind of problem like when you scroll to fast or just drag your tableview with finger cell wasn't able to get correct height. If you found a better solution please post that here so it will be helpful to othersCesium
@PrashantTukadiya of course, I will. Thank you for your help!Derek
Very interesting problem, look for project I create on GitHub github.com/barbados88/cell-table-cell, later I'll try to do it without workaround. Sorry for dirty codePastoralize
@AlexandrKolesnik Thanks! Looking right now...Derek
@AlexandrKolesnik I didn't get why in workaround() function we should go through foreach(), because you just do the same thing n times. However, it works properly. Explanation with better precision would be appreciated!Derek
does this solution work? Because it's workaround)) Each time after reload cell layout is updating, but it depends on the height of your cell** height, if it is static you can delete workaround and write your implementation))Pastoralize
@AlexandrKolesnik so the n is not required number of operation, it becomes resized properly after random number of operation. Am I right?Derek
you can test it yourself, I think the number of operations is n-1, 100% it is not random, but it depends on cell height, you can count it screenHeight / cellHeight or get number of VisibleCells in parent tableViewPastoralize
@Erumaru but what is the reason you decided to nest the tableviews? Can you share a design mock? Maybe there is another good way to implement the UI.Vigilante
@Vigilante Yeah, I've already done so, but still I'm really interested in understanding how automatic height is calculated and I believed that creating question with bounty would let me learn a bit more.Derek
Remember to disable scroll on a tableView inside cellMia
V
3

This is actually not an answer to the question, but just an explanation.
(Wrote here because of the character count limitation for the comments).

The thing is that you're trying to insert a vertically scrollable view inside another vertically scrollable view. If you don't disable the nested tableview's scroll ability, you will have a glitch while scrolling, because the system wouldn't know to whom pass the scroll event (to the nested tableview, or to the parent tableview). So in our case, you'll have to disable the "scrollable" property for the nested tableviews, hence you'll have to set the height of the nested tableview to be equal to its content size. But this way you will lose the advantages of tableview (i.e. cell reusing advantage) and it will be the same as using an actual UIScrollView. But, on the other hand, as you'll have to set the height to be equal to its content size, then there is no reason to use UIScrollView at all, you can add your nested cells to a UIStackView, and you tableview will have this hierarchy:

*TableView
**TableViewCell
***StackView
****Items
****Items
****Items
****Items

But again, the right solution is using multi-sectional tableview. Let your cells be section headers of the tableview, and let inner cells be the rows of the tableview.

Vigilante answered 30/7, 2019 at 7:49 Comment(5)
very interesting idea I didn't reach out myself, thanks!Derek
@Erumaru but again, the right solution is using multi-sectional tableview. Let your cells be section header, and inner cells be the rows of the tableviewVigilante
that's my solution :)Derek
Good to know :)Vigilante
Please, edit your answer a bit, so it will contain your suggestion about multi-sectional tableview. I believe it will help someone :)Derek
A
0

here is an example of how to make a tableview inside a table view cell with automatic height for the cells.

You should use the 'ContentSizedTableView' class for the inner tableViews.

class ViewController: UIViewController {

    @IBOutlet weak var outerTableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        outerTableView.rowHeight = UITableView.automaticDimension
        outerTableView.estimatedRowHeight = UITableView.automaticDimension
        outerTableView.delegate = self
        outerTableView.dataSource = self
    }

}

final class ContentSizedTableView: UITableView {
    override var contentSize:CGSize {
        didSet {
            invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        layoutIfNeeded()
        sizeToFit()
        return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
    }
}

extension ViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        return 10
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? TableTableViewCell

        return cell!
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return UITableView.automaticDimension
    }
}
Apennines answered 26/7, 2019 at 12:9 Comment(2)
I don't see inner tableView in your solution :). I have then same workaround in question details.Derek
Then, contentSize of cells with automaticDimension are equal to estimatedRowHeight, it would be setting tableView height = estimatedRowHeight * numberOfRows.Derek
T
0
  • Use xib files to simplify the hierarchy.
  • Get a tableView on your storyboard, and create a nib file for your tableViewCell(say CustomTableViewCell). Inside it create a tableView and again create one more tableViewCell xib file. Now, no need of setting labels into your xib file,(if you want only labels in cells and nothing else, if not, there is another way of adding constraints)
  • Say you have an array of text, some strings are long and some are short.
  • register nib file in CustomTableViewCell and extend it to use Delegate and DataSource.
  • register this CustomTableViewCell in ViewController.
  • While declaring a cell in CustomTableViewCell, just do=
  • cell.textLabel?.text = content
  • cell.textLabel?.numberOfLines = 0
  • Use heightForRowAt to set outer tableViewCell's height, and let the inner tableView to scroll inside.
Taite answered 26/7, 2019 at 12:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.