I have a ViewController with a CollectionView and would like to put the UICollectionViewDataSource in an extra-class, so that it can be reused. (I'm not using the interface builder).
Therefore, I created an extra-class:
class MyDataSource: NSObject, UICollectionViewDelegate, UICollectionViewDataSource {
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
public func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MYIDENTIFIER", for: indexPath)
cell.backgroundColor = .blue
return cell
}
}
In the ViewController with the CollectionView, I set it:
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "MYIDENTIFIER")
let myDataSource = MyDataSource()
collectionView.delegate = myDataSource
collectionView.dataSource = myDataSource
}
However, the collectionView will stay empty - there are no cells on it.
However, if I put all the DataSource Functions into the ViewController class itself and set the delegate and DataSource to self, then everything works.
Is it not possible to outsource the UICollectionViewDataSource in other files, or is there anything more you have to do?
Just in case. Here is the reason I was saying about calling the init method. #Andrey Chernukha, sorry if it caused any confusion. This is why I said that it works with the init but I guess it is the same reason as Pascal
import UIKit
class ViewController: UIViewController {
lazy var collectionView : UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .green
return cv
}()
var dataSource : CustomDataSource!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.view.addSubview(collectionView)
collectionView.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height)
dataSource = CustomDataSource(collectionView: collectionView)
}
}
class CustomDataSource : NSObject, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UICollectionViewDelegate {
let collectionView : UICollectionView
init(collectionView: UICollectionView) {
self.collectionView = collectionView
super.init()
self.collectionView.dataSource = self
self.collectionView.delegate = self
self.collectionView.register(Cell.self, forCellWithReuseIdentifier: "cell")
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.backgroundColor = .red
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 100, height: 100)
}
}
class Cell : UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
func setupViews() {
self.backgroundColor = .red
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I tested it and the reason is that dataSource is weak in a collectionView so it is deallocated immediately after it's instantiated if declared in viewDidLoad()
thanks for the help! I tried what Galo suggested, but for me this didn't work. However, I found the solution:
You have to initialize the delegate as property of the ViewController, and not in its viewDidLoad function! Then everything is working correctly!
let myDataSource = MyDataSource()
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "MYIDENTIFIER")
collectionView.delegate = myDataSource
collectionView.dataSource = myDataSource
}
It's another scope issue often been overlooked.
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "MYIDENTIFIER")
let myDataSource = MyDataSource(). //See the problem here?
collectionView.delegate = myDataSource
collectionView.dataSource = myDataSource
} //myDataSource will be released!
Related
I have a very similar problem to this question and this question and I think they might be related. The difference is that the delegate is not called when a generic view model is used.
In the first example below everything works as expected. Initialize a ViewController, which is a subclass of SimpleListViewController. When the cell is selected it prints "Here" because the superclass (SimpleListViewController) conforms to UICollectionViewDelegate.
class SimpleListVCModel {
var dataSource: UICollectionViewDiffableDataSource<Section, String>! = nil
var items = Array(0..<10).map{"This is item \($0)"}
enum Section: String {
case main
}
func configureDataSource(using collectionView: UICollectionView) {
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, String> { (cell, indexPath, item) in
var content = cell.defaultContentConfiguration()
content.text = "\(item)"
cell.contentConfiguration = content
}
dataSource = UICollectionViewDiffableDataSource<Section, String>(collectionView: collectionView) {
(collectionView: UICollectionView, indexPath: IndexPath, identifier: String) -> UICollectionViewCell? in
return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: identifier)
}
}
func applySnapshot() {
var snapshot = NSDiffableDataSourceSnapshot<Section, String>()
snapshot.appendSections([.main])
snapshot.appendItems(items)
dataSource.apply(snapshot, animatingDifferences: false)
}
}
class ViewController: SimpleListViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "List"
view.backgroundColor = .red
configureHierarchy()
model.configureDataSource(using: collectionView)
model.applySnapshot()
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Here")
}
}
class SimpleListViewController: UIViewController, UICollectionViewDelegate {
var collectionView: UICollectionView! = nil
let model = SimpleListVCModel()
func configureHierarchy() {
let layout = UICollectionViewCompositionalLayout.list(using: .init(appearance: .insetGrouped))
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
collectionView.delegate = self
view.addSubview(collectionView)
}
}
Now If I introduce generics, the collectionView delegate function is never called.
First create a subclass of SimpleListVCModel specifically for the ViewController.
class ViewControllerViewModel: SimpleListVCModel {
func foo(){
print("Foo")
}
}
Then slightly modify SimpleListViewController to use a generic SimpleListVCModel.
class SimpleListViewController<M: SimpleListVCModel>: UIViewController, UICollectionViewDelegate {
let model: M
var collectionView: UICollectionView! = nil
//MARK: - Initializer
init(model: M) {
self.model = model
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK: - Views
func configureHierarchy() {
let layout = UICollectionViewCompositionalLayout.list(using: .init(appearance: .insetGrouped))
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
collectionView.delegate = self
view.addSubview(collectionView)
}
}
Finally modify ViewController to use a generic version of the SimpleListVCModel.
class ViewController: SimpleListViewController<ViewControllerViewModel> {
override init(model: ViewControllerViewModel = ViewControllerViewModel()) {
super.init(model: model)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "List"
view.backgroundColor = .red
configureHierarchy()
model.configureDataSource(using: collectionView)
model.applySnapshot()
model.foo()
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Here")
}
}
This does appear to be similar to the case in the answer I linked to in comments, but since it's a little different I'll post this answer here.
First, in your SimpleListViewController, implement didSelectItemAt:
class SimpleListViewController<M: SimpleListVCModel>: UIViewController, UICollectionViewDelegate {
let model: M
var collectionView: UICollectionView! = nil
//MARK: - Initializer
init(model: M) {
self.model = model
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK: - Views
func configureHierarchy() {
let layout = UICollectionViewCompositionalLayout.list(using: .init(appearance: .insetGrouped))
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
collectionView.delegate = self
view.addSubview(collectionView)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Selected item: \(indexPath) in SimpleListViewController")
}
}
When you do that, you'll see an error in ViewController:
Overriding declaration requires an 'override' keyword
For the moment, comment out your didSelectItemAt func in ViewController.
Now, when you select an item, didSelectItemAt in SimpleListViewController will be called and it will print to the debug console.
Next, un-comment didSelectItemAt in ViewController and add the override keyword:
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Selected item: \(indexPath) in ViewController")
}
Running the app now and selecting an item will call that func and print to the debug console.
Note that you can also call super ... so, if you have some default code you want executed in SimpleListViewController:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Selected item: \(indexPath) in SimpleListViewController")
// do something common
}
and in ViewController:
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
super.collectionView(collectionView, didSelectItemAt: indexPath)
print("Selected item: \(indexPath) in ViewController")
}
and you'll get both print statements in the debug console.
If you'd rather not do it that way, you can remove the implementation from SimpleListViewController and instead declare the #objc method signature in ViewController:
class ViewController: SimpleListViewController<ViewControllerViewModel> {
override init(model: ViewControllerViewModel = ViewControllerViewModel()) {
super.init(model: model)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "List"
view.backgroundColor = .red
configureHierarchy()
model.configureDataSource(using: collectionView)
model.applySnapshot()
model.foo()
}
#objc (collectionView:didSelectItemAtIndexPath:)
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Selected item: \(indexPath) in ViewController")
}
}
Note that you can no longer call super with that approach.
I am trying to create a similar layout to the App store. I have got one collection view that is vertical and inside of it each row is another collection view that is horizontal.
I am trying to use dynamic height so the cell can increase its height based on the content inside. However that never works, it only works when i use sizeForItemAt function to explicitly set it. I would like to also be able to control the width
I have looked at many previous questions such as: UICollectionView, full width cells, allow autolayout dynamic height? however no answer worked for me.
I am really confused on what i am doing wrong.
import UIKit
class HomeViewController: UICollectionViewController {
public override func viewDidLoad() {
super.viewDidLoad()
let collectionViewLayout = UICollectionViewFlowLayout()
collectionViewLayout.scrollDirection = .vertical
collectionViewLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
collectionView.collectionViewLayout = collectionViewLayout
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView?.register(HorizontalCollectionView.self, forCellWithReuseIdentifier: HorizontalCollectionView.reuseIdentifier)
}
public override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
public override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 4
}
public override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: HorizontalCollectionView.reuseIdentifier, for: indexPath) as? HorizontalCollectionView {
return cell
}
return UICollectionViewCell()
}
}
HorizontalCollectionView.swift
import UIKit
class HorizontalCollectionView: UICollectionViewCell, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
static let reuseIdentifier = "HorizontalCollectionView"
var cellSpancolumns: CGFloat?
override init(frame: CGRect) {
super.init(frame: frame)
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
collectionView.delegate = self
collectionView.dataSource = self
addSubview(collectionView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented - Storyboards are not used.")
}
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
let collectionView = UICollectionView(frame: bounds, collectionViewLayout: layout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.showsHorizontalScrollIndicator = false
return collectionView
}()
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
if (indexPath as NSIndexPath).section == 0 {
cell.backgroundColor = UIColor.blue
} else {
cell.backgroundColor = UIColor.red
}
return cell
}
}
Newbie trying to learn programming concepts with swift and Cocoa Touch
So, I have once custom CategoryCell: UICollectionViewCell
class CategoryCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupView() {
print("set background Color")
backgroundColor = UIColor.black
}
}
Paired with one programatically created UICollectionView
class FeaturedAppsController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
private let cellID = "cellID"
private let featuredApps = "Featured Apps"
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = featuredApps
collectionView?.backgroundColor = UIColor.yellow
collectionView?.register(CategoryCell.self, forCellWithReuseIdentifier: cellID)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID , for: indexPath) as! CategoryCell
print("Cell created")
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 150)
}
}
Code under App
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
let layout = UICollectionViewLayout()
let featuredAppsController = FeaturedAppsController(collectionViewLayout: layout)
window?.rootViewController = UINavigationController(rootViewController: featuredAppsController)
return true
}
I've registered the custom cell in viewDidLoad();
Also, I'm making it's width to be equal to the view's width.
But all it shows me is UICollectionView with yellow color, that's it 🤷🏻♂️
try this. you missing some codes. you should have
#IBOutlet weak var collectionView: UICollectionView!
and also u missing this datasourse and delegates of collectionView
collectionView.dataSource = self
collectionView.delegate = self
this is the full code.
import UIKit
class ViewController: UIViewController,UICollectionViewDelegate,UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
collectionView.delegate = self
// Do any additional setup after loading the view, typically from a nib.
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
print("Cell created")
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 150)
}
}
Try this and let me know.
Create your xib files and then register your cell with collection view
//Register cell
let cellNIB = UINib(nibName: "YourCollectionViewXibName", bundle: nil)
collectionView.register(cellNIB, forCellWithReuseIdentifier: cellID)
If it's all programmatically then init it with layout like this
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
let featuredAppsController = FeaturedAppsController(collectionViewLayout: layout)
window?.rootViewController = UINavigationController(rootViewController: featuredAppsController)
You are using UICollectionViewLayout()
You should use:
let layout = UICollectionViewFlowLayout()
I created a UICollectionView as a major UICollectionView and each collectionViewCell has another UICollectionView "B" as it's subview
Then I add a tapGesture in "B". but "B" can't trigger this gesture.
Following is my main code:
Main UICollectionView
import Foundation
import UIKit
class mainViewController :UIViewController,UICollectionViewDataSource,UICollectionViewDelegate {
#IBOutlet weak var mainCollectionView: UICollectionView!
override func viewDidLoad() {
self.mainCollectionView.registerNib(UINib(nibName: "OuterCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "OuterCollectionViewCell")
self.mainCollectionView.delegate = self
self.mainCollectionView.dataSource = self
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return 1
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("OuterCollectionViewCell", forIndexPath: indexPath) as! OuterCollectionViewCell
return cell
}
}
UICollectionView B:
class OuterCollectionViewCell: UICollectionViewCell,UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet weak var InnerCollectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
InnerCollectionView.registerNib(UINib(nibName: "InnerCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "CellInner")
}
func handleTapGesture(sender: UITapGestureRecognizer) {
print("tapguesture")
}
override func didMoveToWindow(){
let tapGesture = UITapGestureRecognizer(target: self, action: "handleTapGesture:")
InnerCollectionView.backgroundColor = UIColor.redColor()
InnerCollectionView.addGestureRecognizer(tapGesture)
InnerCollectionView.delegate = self
InnerCollectionView.dataSource = self
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("CellInner", forIndexPath: indexPath) as! InnerCollectionViewCell
return cell
}
}
This issue is bugging me an whole day. Is there anyone who has any ideas about how to fix this issue?
I have uploaded the project into github: https://github.com/dengchicheng/Test2
update------------------------------
today I init the innerCollectionView by code programmably instead of init by NIB. and it works
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = UICollectionViewScrollDirection.Horizontal
let InnerCollectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: 800, height: 600), collectionViewLayout: flowLayout)
I don't know what the NIB did to the UIcollectionview
I did set the userInteractive to be true in the interface builder
I am interested in having a collectionview as a part of a collection view cell but for some reason cannot figure out how this would be done. Where would I implement the necessary methods for the cells collectionview?
There's an article that Ash Furrow wrote that explains how to put an UICollectionView inside an UITableViewCell. It's basically the same idea when using it inside an UICollectionViewCell.
Everything is done programatically. No storyboards.
I added a UICollectionView inside my UICollectionViewCell. I also show how to add again a UICollectionViewCell inside the created UICollectionView to have this result
import UIKit
class CategoryCell: UICollectionViewCell, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
private let cellId = "cell"
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let appsCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
return collectionView
}()
func setupViews() {
backgroundColor = .blue
addSubview(appsCollectionView)
appsCollectionView.delegate = self
appsCollectionView.dataSource = self
appsCollectionView.register(AppCell.self, forCellWithReuseIdentifier: cellId)
addConstrainstWithFormat("H:|-8-[v0]-8-|", views: appsCollectionView)
addConstrainstWithFormat("V:|[v0]|", views: appsCollectionView)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath)
return cell
}
}
class AppCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews(){
backgroundColor = .red
}
}
My UICollectionViewController
import UIKit
class FeaturedAppsController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
let cellId = "cell"
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
collectionView?.backgroundColor = .white
collectionView?.register(CategoryCell.self, forCellWithReuseIdentifier: cellId)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(view.frame.width, 150)
}
}
The whole explanation can be found and was developed by "Let´s build that app": https://www.youtube.com/watch?v=Ko9oNhlTwH0&list=PL0dzCUj1L5JEXct3-OV6itP7Kz3tRDmma
This is too late for this answer but it might help others. This is an example of UICollectionView inside a UICollectionViewCell.
Lets start by having a mainCollectionView. Then on each cell of this collection create and initialize a new UICollectionView and right place to do that is in this following delegate of UICollectionView
func collectionView(collectionView: UICollectionView, willDisplayCell cell: UICollectionViewCell, forItemAtIndexPath indexPath: NSIndexPath)
e.g I initialize the MainCollectionViewCell here and then MainCollectionViewCell handles the logic to create a new UICollectionView
guard let collectionViewCell = cell as? MainCollectionViewCell else { return }
collectionViewCell.delegate = self
let dataProvider = ChildCollectionViewDataSource()
dataProvider.data = data[indexPath.row] as NSArray
let delegate = ChildCollectionViewDelegate()
collectionViewCell.initializeCollectionViewWithDataSource(dataProvider, delegate: delegate, forRow: indexPath.row)
collectionViewCell.collectionViewOffset = storedOffsets[indexPath.row] ?? 0
Here is the initializer on MainCollectionViewCell that creates a new UICollectionView
func initializeCollectionViewWithDataSource<D: protocol<UICollectionViewDataSource>,E: protocol<UICollectionViewDelegate>>(dataSource: D, delegate :E, forRow row: Int) {
self.collectionViewDataSource = dataSource
self.collectionViewDelegate = delegate
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .Horizontal
let collectionView = UICollectionView(frame: self.bounds, collectionViewLayout: flowLayout)
collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseChildCollectionViewCellIdentifier)
collectionView.backgroundColor = UIColor.whiteColor()
collectionView.dataSource = self.collectionViewDataSource
collectionView.delegate = self.collectionViewDelegate
collectionView.tag = row
self.addSubview(collectionView)
self.collectionView = collectionView
collectionView.reloadData()
}
Hope that helps !!
I did an example for this and put in on github. It demonstrates the use UICollectionView inside a UICollectionViewCell.
https://github.com/irfanlone/Collection-View-in-a-collection-view-cell
Easiest solution for collectionview inside collectionview using storyboard and Swift 5
Please refer this link for nested collectionview example
import UIKit
class ParentViewController:UIViewController,UICollectionViewDataSource,UICollectionViewDelegate {
//MARK: Declare variable
//MARK: Decalare outlet
#IBOutlet weak var outerCollectionView: UICollectionView!
#IBOutlet weak var pageControl: UIPageControl!
let outerCount = 4
//MARK: Decalare life cycle methods
override func viewDidLoad() {
super.viewDidLoad()
pageControl.numberOfPages = outerCount
}
//MARK: Collection View delegate methods
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return outerCount
}
//MARK: Collection View datasource methods
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "OuterCell", for: indexPath) as! OuterCollectionViewCell
cell.contentView.backgroundColor = .none
return cell
}
//MARK:- For Display the page number in page controll of collection view Cell
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let visibleRect = CGRect(origin: self.outerCollectionView.contentOffset, size: self.outerCollectionView.bounds.size)
let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
if let visibleIndexPath = self.outerCollectionView.indexPathForItem(at: visiblePoint) {
self.pageControl.currentPage = visibleIndexPath.row
}
}
}
class OuterCollectionViewCell: UICollectionViewCell ,UICollectionViewDataSource,UICollectionViewDelegate {
#IBOutlet weak var InnerCollectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
InnerCollectionView.delegate = self
InnerCollectionView.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
6
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "InnerCell", for: indexPath) as! InnerCollectionViewCell
cell.contentView.backgroundColor = .green
return cell
}
}
class InnerCollectionViewCell: UICollectionViewCell{
}