Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add UICollectionView in UICollectionViewCell

I am using Swift to build an iOS application for the Hospital I work at.

Somehow, in a specific feature I have to put a UICollectionView inside the UICollectionViewCell. The one I want to achieve was for every content of the parent UICollectionView (vertical scrolling) would have several children (Which can be scrolled horizontal) depending on the parent row.

For illustration, I have to display list of doctors (name & photo) and then I have to display each of the practice schedule of them below their name and photo. The practice schedule would vary depending on each doctor. So, I have to put it inside the UICollectionView.

I have tried several solutions that I found on the web, but I still cannot approach it.

The most problem that I can't solve was: I don't know where is the place in the code to load the child data source (doctor schedule) and when I could load it, because I can't have two functions like below:

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

this is the one I want to achieve

design of collection view

the UIImage and doctor name (UILabel) was in the parent UICollectionViewCell (scroll vertically), and then everything in the box (practice day n practice time) are the child UICollectionView (scroll horizontally)

PS: there are many doctors, and each of the doctor has several practice day.

please help me how to do this

like image 519
christ2702 Avatar asked Dec 04 '22 22:12

christ2702


2 Answers

If you really want to insert an collectionView inside a collectionViewCell then there is a pretty simple step. Create an instance of UICollectionView and add it the collectionViewCell. You can use this example if you like.

//
//  ViewController.swift
//  StackOverFlowAnswer
//
//  Created by BIKRAM BHANDARI on 18/6/17.
//  Copyright © 2017 BIKRAM BHANDARI. All rights reserved.
//

import UIKit

class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {

    let cellId = "CellId"; //Unique cell id

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .red; //just to test

        collectionView.register(Cell.self, forCellWithReuseIdentifier: cellId) //register collection view cell class
        setupViews(); //setup all views
    }

    func setupViews() {

        view.addSubview(collectionView); // add collection view to view controller
        collectionView.delegate = self; // set delegate
        collectionView.dataSource = self; //set data source

        collectionView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true; //set the location of collection view
        collectionView.rightAnchor.constraint(equalTo:  view.rightAnchor).isActive = true; // top anchor of collection view
        collectionView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true; // height
        collectionView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true; // width

    }

    let collectionView: UICollectionView = { // collection view to be added to view controller
        let cv = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()); //zero size with flow layout
        cv.translatesAutoresizingMaskIntoConstraints = false; //set it to false so that we can suppy constraints
        cv.backgroundColor = .yellow; // test
        return cv;
    }();

    //deque cell
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath);
//        cell.backgroundColor = .blue;
        return cell;
    }

    // number of rows
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 5;
    }

    //size of each CollecionViewCell
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: view.frame.width, height: 200);
    }
}

// first UICollectionViewCell
class Cell: UICollectionViewCell, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {

    let cellId = "CellId"; // same as above unique id

    override init(frame: CGRect) {
        super.init(frame: frame);

        setupViews();
        collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId); //register custom UICollectionViewCell class.
        // Here I am not using any custom class

    }


    func setupViews(){
        addSubview(collectionView);

        collectionView.delegate = self;
        collectionView.dataSource = self;

        collectionView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true;
        collectionView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true;
        collectionView.topAnchor.constraint(equalTo: topAnchor).isActive = true;
        collectionView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true;
    }

    let collectionView: UICollectionView = {
        let layout = UICollectionViewFlowLayout();
        layout.scrollDirection = .horizontal; //set scroll direction to horizontal
        let cv = UICollectionView(frame: .zero, collectionViewLayout: layout);
        cv.backgroundColor = .blue; //testing
        cv.translatesAutoresizingMaskIntoConstraints = false;
        return cv;
    }();

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath);
        cell.backgroundColor = .red;
        return cell;
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 5;
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: self.frame.width, height: self.frame.height - 10);
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

Sample screenshot

like image 90
Jivan Bhandari Avatar answered Dec 20 '22 23:12

Jivan Bhandari


This might be a little late, but for people out here still trying to find an answer.

After some research and digging, I stumbled upon several posts stating reasons why you should NOT have your cell be the delegate for you collectionView. So, I was lost because pretty much all solutions I had found were doing this, until I finally found what I believe is the best way to have nested collectionViews.

To give some background, my app included not only one but 2 collectionViews inside different cells of another collectionView, so setting the delegates with tags and all that, wasn't really the best approach nor the correct OO solution.

So the best way to do it is the following:

First you have to created a different class to serve as your delegate for the inner collectionView. I did it as such:

class InnerCollectionViewDelegate: NSObject, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
 // CollectionView and layout delegate methods here
 // sizeForItemAt, cellForItemAt, etc...
}

Now, in your inner collectionView (or rather the cell where you have the inner collectionView) create a function that will allow you to set its delegates

class InnerCell: UICollectionViewCell {

    var collectionView: UICollectionView

    init() {
        let layout = UICollectionViewFlowLayout()
        collectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: frame.width, height: frame.height), collectionViewLayout: layout)
     }

    func setCollectionViewDataSourceDelegate(dataSourceDelegate: UICollectionViewDataSource & UICollectionViewDelegate) {
            collectionView.delegate = dataSourceDelegate
            collectionView.dataSource = dataSourceDelegate
            collectionView.reloadData()
    }
}

And lastly, in your ViewController where you have your outermost (main) collectionView do the following:

First instantiate the delegate for the inner collectionView

var innerDelegate = InnerCollectionViewDelegate()

and then

override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        if let cell = cell as? InnerCell {
            cell.setCollectionViewDataSourceDelegate(dataSourceDelegate: innerDelegate)
        }
}

This might not be perfect, but at least you have separation of concerns, as your cell should NOT be the delegate. Remember your cell should only be responsible for displaying info, not trying to figure out what the size of the collectionView should be, etc.

I did find similar answers that dealt with setting the collectionViews tag and whatnot, but I found that that made it way harder to deal with each collectionView individually, plus dealing with tags can't result in spaghetti code or unintended behaviours.

I left out registering and dequeuing the cell, but I'm sure you're all familiar with that. If not, just let me know and I'll try to walk you through it.

like image 32
tapizquent Avatar answered Dec 20 '22 23:12

tapizquent