4

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

jamryu
  • 668
  • 10
  • 24
christ2702
  • 473
  • 6
  • 12
  • 26

4 Answers4

11

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

Jivan Bhandari
  • 860
  • 1
  • 10
  • 32
5

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.

tapizquent
  • 668
  • 11
  • 24
1

There are multiple ways to tackle the problem of a horizontal collection inside another a vertical list collection.

The simplest would be to make the ViewController you are presenting the main UICollectionView to the dataSouce and delegate for both collection views. You can set the collection view inside the cell also to be served from here.

This article about placing collection view inside a table view explains the problem in a much elaborate way and the code for the same can be found here.

carbonr
  • 6,049
  • 5
  • 46
  • 73
  • I already try that, but I still don't get it. I don't know how to implement it in project – christ2702 Jun 19 '17 at 00:52
  • Same! I have been trying to find the right way to implement this. My collectionView includes not 1, but 2 other collectionViews inside (2 of my cells include collectionViews), but have yet to find the right solution. – tapizquent Mar 10 '19 at 02:46
  • Found a better solution. Check my answer below – tapizquent Mar 11 '19 at 20:08
0

Add collectionView in collection view cell , and add delagate methods in collectionviewclass.swift. Then pass list you want to show in cell in collectionview's cellforrowatindexpath. If you didn't success on implimenting it then let me know . i will provide you code as i have already implemented it in that way.

Jitendra Tanwar
  • 249
  • 2
  • 11
  • I'm sorry, I just didn't know how to perform it – christ2702 Jun 19 '17 at 00:47
  • the main problem that I didn't understand was, did I must have two collection view class or just on for the parent collection view? and how to pass the list from the child to parent? – christ2702 Jun 19 '17 at 01:56