swiftuitableviewuicollectionviewuicollectionviewcellpresentmodalviewcontroller

UICollectionView inside UITableViewCell shows only one cell


I have a TableView.

Each UITableViewCell is a UICollectionView.

Each UICollectionViewCell has only an image.

For some reason, my UICollectionView shows only one cell, even though my model has more.

What am I missing here?

Sharing my code:

ProductsVC

import UIKit

class ProductsVC: UIViewController
{
    @IBOutlet weak var categories: UITableView!

    override func viewDidLoad()
    {
        super.viewDidLoad()

        categories.delegate = self
        categories.dataSource = self

        self.categories.separatorStyle = .none
        self.categories.register(UINib(nibName: "CategoryCell", bundle: nil), forCellReuseIdentifier: "categoryCell")
    }
}

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

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
    {

        let cell = categories.dequeueReusableCell(withIdentifier: "categoryCell", for: indexPath) as! CategoryCell

        cell.listOfProducts = Utils.inventory[indexPath.section].listOfProducts

        return cell
    }

    func numberOfSections(in tableView: UITableView) -> Int
    {
        return Utils.inventory.count
    }

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
    {
        let headerText = UILabel()
        headerText.textColor = UIColor.systemPink
        headerText.adjustsFontSizeToFitWidth = true
        headerText.textAlignment = .right
        headerText.frame = CGRect(x: 20, y: 20, width: UIScreen.main.bounds.width - 40, height: 20)
        headerText.font = UIFont.boldSystemFont(ofSize: 25)

        headerText.text = Utils.inventory[section].name
        headerText.backgroundColor = .clear

        let headerView = UIView()
        headerView.addSubview(headerText)

        return headerView
    }
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
    {
        let bounds = UIScreen.main.bounds
        let height = bounds.size.height

        return (height - (CGFloat(Utils.inventory.count) * 70))/CGFloat(Utils.inventory.count)
    }
}

CategoryCell

import UIKit

class CategoryCell: UITableViewCell
{
    @IBOutlet private weak var products: UICollectionView!
    var listOfProducts: [Product] = []

    override func awakeFromNib()
    {
        super.awakeFromNib()

        self.products.delegate = self
        self.products.dataSource = self

        self.products.register(UINib(nibName: "ProductCell", bundle: nil), forCellWithReuseIdentifier: "productCell")
    }
    override func setSelected(_ selected: Bool, animated: Bool)
    {
        super.setSelected(selected, animated: animated)
    }
}

extension CategoryCell: UICollectionViewDelegate, UICollectionViewDataSource
{
    func numberOfSections(in collectionView: UICollectionView) -> Int
    {
        return 1
    }


    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
    {
        return listOfProducts.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        let cell = products.dequeueReusableCell(withReuseIdentifier: "productCell", for: indexPath) as! ProductCell

        cell.productImage.image = listOfProducts[indexPath.row].image

        let height = UIScreen.main.bounds.height
        let frameHeight = ((height - (CGFloat(Utils.inventory.count) * 70))/CGFloat(Utils.inventory.count)) - 60

        cell.frame = CGRect(x: 20, y: 50, width: frameHeight, height: frameHeight)
        cell.productImage.frame = CGRect(x: 0, y: 0, width: frameHeight, height: frameHeight)

        Utils.makeControlRound(layer: cell.layer, borderWidth: 1, cornerRadius: 25, borderColor: cell.layer.backgroundColor)

        return cell
    }
}

ProductCell

import UIKit

class ProductCell: UICollectionViewCell
{
    @IBOutlet weak var productImage: UIImageView!

    override func awakeFromNib()
    {
        super.awakeFromNib()

        Utils.makeControlRound(layer: self.layer, borderWidth: 1, cornerRadius: 25, borderColor: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 0))
        Utils.makeControlRound(layer: self.productImage!.layer, borderWidth: 1, cornerRadius: 25, borderColor: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 0))
    }
}

Also - is there a way to present (show modally) a different UIViewController when I click the UICollectionViewCell?


Solution

  • You're going to want to pass a closure to the tableView cell so that when one of the collectionView cells is selected the tableView controller will know about it. onProductSelected is what you want to be paying attention to here.

    class CategoryCell: UITableViewCell {
        var onProductSelected: ((Product) -> Void)?
        var listOfProducts: [Product] = []
    }
    
    extension CategoryCell: UICollectionViewDelegate, UICollectionViewDataSource {
        func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
            onProductSelected?(listOfProducts[indexPath.item])
        }
    }
    
    class ProductsVC: UIViewController {
        @IBOutlet weak var categories: UITableView!
    
        func presentViewController(for product: Product) {
            // Whatever
        }
    }
    
    extension ProductsVC: UITableViewDelegate, UITableViewDataSource {
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = categories.dequeueReusableCell(withIdentifier: "categoryCell", for: indexPath) as! CategoryCell
    
            cell.onProductSelected = { [unowned self] product in
                self.presentViewController(for: product)
            }
            cell.listOfProducts = [] // Whatever
    
            return cell
        }
    }
    

    Hope that helps.