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
    }
}

えるまる
  • 2,409
  • 3
  • 24
  • 44
  • 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 contentSize – Alexandr Kolesnik Jul 26 '19 at 09:22
  • @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`. – えるまる Jul 26 '19 at 09:32
  • https://stackoverflow.com/questions/48031969/tableview-automatic-dimension-tableview-inside-tableview – Prashant Tukadiya Jul 26 '19 at 10:08
  • @PrashantTukadiya sorry, but your solution looks crutch for me. I need something more stable. – えるまる Jul 26 '19 at 10:30
  • @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 others – Prashant Tukadiya Jul 26 '19 at 11:26
  • @PrashantTukadiya of course, I will. Thank you for your help! – えるまる Jul 26 '19 at 11:32
  • 1
    Very interesting problem, look for project I create on GitHub https://github.com/barbados88/cell-table-cell, later I'll try to do it without workaround. Sorry for dirty code – Alexandr Kolesnik Jul 26 '19 at 11:35
  • @AlexandrKolesnik Thanks! Looking right now... – えるまる Jul 26 '19 at 11:45
  • @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! – えるまる Jul 26 '19 at 12:25
  • 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)) – Alexandr Kolesnik Jul 26 '19 at 12:29
  • @AlexandrKolesnik so the `n` is not required number of operation, it becomes resized properly after `random` number of operation. Am I right? – えるまる Jul 26 '19 at 13:46
  • 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 tableView – Alexandr Kolesnik Jul 29 '19 at 05:31
  • @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. – arturdev Jul 30 '19 at 06:47
  • @arturdev 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. – えるまる Jul 30 '19 at 07:33
  • Remember to disable scroll on a tableView inside cell – T. Pasichnyk Aug 01 '19 at 15:15

3 Answers3

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.

arturdev
  • 10,884
  • 2
  • 39
  • 67
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
    }
}
valentinv
  • 139
  • 5
  • I don't see inner tableView in your solution :). I have then same workaround in question details. – えるまる Jul 26 '19 at 12:23
  • Then, `contentSize` of cells with `automaticDimension` are equal to `estimatedRowHeight`, it would be setting `tableView` height = `estimatedRowHeight * numberOfRows`. – えるまる Jul 26 '19 at 12:28
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.
fin
  • 77
  • 1
  • 3