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
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.
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.
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")
}
}
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.