Search code examples
swifttableviewcellcollectionview

How can l put TableView inside CollectionViewCell?


I have a tableView with several cells (created by MVVM-architecture).

In ViewController I fill my tableView like this:

tbv.registerCells(
        withModels:
        FirstViewModel.self,
        SecondViewModel.self,
        ThirdViewModel.self)

My task is to put my TableView in one cell of CollectionView. I thought l have to create CollectionView in my ViewController, after it create CollectionViewCell and CollectionCellViewModel, but how to do it exactly I don't understand.

If you know, how to make it, help.


Solution

  • How I have several tableviews in collection views in one of my apps. First I have a view controller in which I build my collection view. As usually proposed in the new design guidelines, I have the Collection View delegate and data source in an extension of this view controller.

    In your view controller you define a delegate and data source for your table view. Preferably, this is a different class. I would not have the tableview data source and delegate also in the same view controller as your collection view.

    class WorkoutSettingsViewController: UIViewController, LoadWorkoutSettings {
    
        //MARK: - Properties
        //Used variables
    
        //Used constants
        private let settingsDelegate = SettingsTableViewDelegate()
    

    The extension would then look like this.

    extension WorkoutSettingsViewController: UICollectionViewDataSource, UICollectionViewDelegate {
        
        func numberOfSections(in collectionView: UICollectionView) -> Int {
            //Whatever sets your sections
        }
        
        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
            //Whatever sets your rows per section
        }
    
        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
            
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Workout Settings", for: indexPath) as! SettingsCollectionViewCell
                
            settingsDelegate.workoutTitleLabel = [countdown, mainView, spokenMessage]
            settingsDelegate.mainContentLabel = getSettingsContent()
            cell.settingsTableView.delegate = settingsDelegate
            cell.settingsTableView.dataSource = settingsDelegate
            cell.settingsTableView.reloadData()
    
            return cell
        }
        
        func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
            
         //Whatever you need as header or footer
    }
    

    The delegate does exactly what you would like the table view data source and delegate to do.

    class SettingsTableViewDelegate: NSObject, UITableViewDataSource, UITableViewDelegate {
        
        //MARK: - Properties
        //Used variables
        var workoutTitleLabel = [String]()
        var mainContentLabel = [String]()
        var selectedSetting: ((Int) -> ())? = .none
        private var secondaryContentLabel = [String]()
        
        //Used constants
        private let onTitle = NSLocalizedString("ON", comment: "Localized on title")
        private let offTitle = NSLocalizedString("OFF", comment: "Localized off title")
        private let fontColorBlack = UIColor(red: 20.0/255.0, green: 20.0/255.0, blue: 19.0/255.0, alpha: 1.0)
        private let fontColorRed = UIColor(red: 255.0/255.0, green: 96.0/255.0, blue: 89.0/255.0, alpha: 1.0)
        
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            workoutTitleLabel.count
        }
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            
            let cell = tableView.dequeueReusableCell(withIdentifier: "Settings Cell") as! WorkoutTableViewCell
            
            cell.workoutTitle.text = workoutTitleLabel[indexPath.row]
            cell.mainContent.text = mainContentLabel[indexPath.row]
            cell.secondaryContent.text = ""
            
            (mainContentLabel[indexPath.row] == offTitle) ? (cell.mainContent.textColor = fontColorRed) : (cell.mainContent.textColor = fontColorBlack)
            
            return cell
        }
        
        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            tableView.deselectRow(at: indexPath, animated: true)
            selectedSetting?(indexPath.row)
        }
        
        func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
            61
        }
    }
    

    Your collection view cell should look like this.

    class SettingsCollectionViewCell: UICollectionViewCell {
        
        @IBOutlet weak var settingsTableView: UITableView!
    }
    

    This should then work. If you need to have a callback from the table view delegate / data source to your view controller managing your collection view, you can use a closure. In the example table view delegate the closure is called selectedSettings. In your view controller in viewDidLoad you define the call back for instance like this:

    override func viewDidLoad() {
        super.viewDidLoad()
        
        settingsDelegate.selectedSetting = { [unowned self] selection in
            startSettingsMenu(for: selection)
        }
        
    }
    

    The result looks like this. enter image description here

    Kind regards, MacUserT