3
votes

I have the following layout

  • UITableView
    • UITableViewCell (class: CategoryCell)
      • Label
      • Button
      • UICollectionView
        • UICollectionViewCell (class: ItemCell)
          • UIImageView
          • UILabel

I am trying to make UICollectionView to always show 3 items no matter the screen size. I was able to get items to be shown based on screen width and was able to set the height. However, the height of table view does not change from inside CategoryCell. Here is the code:

// CategoryCell.swift

func awakeFromNib() {
    super.awakeFromNib()

    // Calculate width and height based on screen width
    let screenWidth = UIScreen.main.bounds.width
    let itemWidth = screenWidth / 3.0
    let itemHeight = itemWidth / 0.75 // 3:4 aspect ratio

    // Change height of table view cell
    self.bounds.size.height = self.bounds.size.height - collectionView.bounds.size.height + itemHeight
    // This is the line does nothing
    // It should remove collectionView height from tableView (height of collectionView is set through autolayout) then add he new height to it.

    // Set collection view height to equal to item height
    collectionView.bounds.size.height = itemHeight

    // set item height
    let layout = collectionViewProducts.collectionViewLayout as! UICollectionViewFlowLayout
    layout.itemSize = CGSize(width: itemWidth, height: itemHeight - 1.0)
    // -1.0 because item/cell height needs to be SMALLER than collection view height
}

How can I change table view cell height from inside the cell class itself? My only guess is that I should not be doing these operations inside awakeFromNib but I am not able to find another function to call these from.

EDIT: I am using RxSwift, so my data source code is the following:

let observable = Observable.just(data)
observable.bindTo(tableView.rx.items(cellIdentifier: "CategoryCell", cellType: CategoryCell.self)) { (row, element, cell) in
    cell.setCategory(category: element)
}.disposed(by: disposeBag)

tableView.rx.setDelegate(self).disposed(by: disposeBag)
4

4 Answers

2
votes

You could implement UITableViewDelegate's heightForRowAt and return a value based on a variable. And you can set the variable wherever you do your UICollectionView itemHeight calculation. So, when you are done with the calculation, you should be able to do a table view data reload and the layout should update using the new itemHeight value.

I have not tested the code but the above should work. If you run into any issues, or I've misunderstood your requirements somehow, do let me know.

1
votes

Provide the constraints to the collection view and make collectionviewcell outlet in tableviewcell page as well as collectionviewheight constraint outlet in the same tableviewcell page. Like this in tableviewcell page:

class HomeServiceTableCell: UITableViewCell {

  @IBOutlet weak var dealsCollectionView: UICollectionView!
  @IBOutlet weak var dealsCollectionHeight: NSLayoutConstraint!
  @IBOutlet weak var dealsImage: UIImageView!

  // like this in the main page cellforRowAt indexPath function

  func tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = homeServiceTable.dequeueReusableCell ( withIdentifier:  "homeServiceTableCell6" ) as! homeServiceTableCell6
    cell.dealsImage.image = UIImage(named: "deals.png")
    cell.dealsCollectionView.tag = indexPath.row
    cell.dealsCollectionView.delegate = self
    cell.dealsCollectionView.dataSource = self
    cell.dealsCollectionView.reloadData()
    cell.dealsCollectionHeight.constant = cell.dealsCollectionView.collectionViewLayout.collectionViewContentSize.height

    return cell
  }

  func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat  {
    return UITableViewAutomaticDimension
  }

  // lastly add this line in viewWillAppear function

  override func viewWillAppear(_ animated: Bool) {
    self.homeServiceTable.layoutSubviews()
  }
}
0
votes

You need to provide tableview's cell height for every cell and based on that you can calculate your collectionview cell's height. for dynamic height of tableview's cell you need to implement these method.

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}

or if you need fixed height then simply use these method and provide your cell height based on your calculation.

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return fixedSize //e.g 100 
}

try this hope it will help you.

0
votes

Thank you @Prince and @Fahim. Your answers gave me an idea to move my row height logic to tableView delegate. Since, I am calculating the height based on screen width and aspect ratio. I just moved the same logic to tableView height:

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    let screenWidth = UIScreen.main.bounds.size.width

    // 3 items + 10px gap between cells
    let itemWidth = (screenWidth / 3.0) - 20

    // 3:4 aspect ratio + other elements
    let itemHeight = (itemWidth) / 0.75 + 110

    return itemHeight
}

For now this solution works normally for me, I would like to change it so it takes into account the cell itself.

I know it is weird to ask a question in the answer but it is related to this thread, so I think it is best to ask it here. How can I get the cell object from indexPath? Using tableView.cellForRow(at: indexPath) gives me bad access.