UICollectionView keep horizontal list scroll position - ios

I have some horizontal UICollectionViewCell in my UICollectionView.
My problem is that if I scroll the horizontal list in the first cell to another position, the fourth cell will also be at that same position.
Same for the second and the fifth, the third and the sixth...
Also don’t keep horizontal scroll position from portrait to landscape or opposite.
Is there a way for the UICollectionView in the cells to keep their position?
Update 2:
I know i have to save inner horizontal collection view content offsets in an array. I read about that in link below, but in the below link they want to achieve this in UITableView and UIScrollView. And i want to achieve this in UICollectionView inside UICollectionViewController.
Scroll View in UITableViewCell won't save position
Updated 1:
https://imgur.com/a/F8EGsTx
ViewController.swift
import UIKit
class ViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
let cellId = "cellId"
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = .white
collectionView?.register(CategoryCell.self, forCellWithReuseIdentifier: cellId)
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! CategoryCell
cell.nameLabel.text = "Horizontal list #\(indexPath.item)"
return cell
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 150)
}
}
CategoryCell.swift
import UIKit
class CategoryCell: UICollectionViewCell, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
let cellId = "categoryId"
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var nameLabel: UILabel = {
let label = UILabel()
label.text = "Horizontal list #1"
label.font = UIFont.systemFont(ofSize: 16)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let appsCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = .white
return collectionView
}()
let dividerLineView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(white: 0.4, alpha: 0.4)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
func setupViews() {
addSubview(appsCollectionView)
addSubview(dividerLineView)
addSubview(nameLabel)
appsCollectionView.dataSource = self
appsCollectionView.delegate = self
appsCollectionView.register(AppCell.self, forCellWithReuseIdentifier: cellId)
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-14-[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": nameLabel]))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-14-[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": dividerLineView]))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": appsCollectionView]))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[nameLabel(30)][v0][v1(0.5)]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": appsCollectionView, "v1": dividerLineView, "nameLabel": nameLabel]))
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! AppCell
cell.imageView.backgroundColor = UIColor(hue: CGFloat(indexPath.item) / 20.0, saturation: 0.8, brightness: 0.9, alpha: 1)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 100, height: frame.height - 32)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsetsMake(0, 14, 0, 14)
}
}
AppCell.swift
import UIKit
class AppCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let imageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFill
iv.layer.cornerRadius = 16
iv.layer.masksToBounds = true
return iv
}()
func setupViews() {
addSubview(imageView)
imageView.frame = CGRect(x: 0, y: 0, width: frame.width, height: frame.width)
}
}

I finally got it :)
Thanks to 3 years old project! from irfanlone
https://github.com/irfanlone/Collection-View-in-a-collection-view-cell
It's works and support interface orientation too, but need some optimization.
For example if you scroll to the right and then scroll down fast and then scroll back to top the cell have a extra margin from the right edge! Also have some issue with interface orientation.
If anyone have a complete version please share that, thanks.
For anyone who may be interested, Add the following code in the specified files.
ViewController.swift
var storedOffsets = [Int: CGFloat]()
override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
guard let collectionViewCell = cell as? CategoryCell else { return }
collectionViewCell.collectionViewOffset = storedOffsets[indexPath.row] ?? 0
}
override func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
guard let collectionViewCell = cell as? CategoryCell else { return }
storedOffsets[indexPath.row] = collectionViewCell.collectionViewOffset
}
CategoryCell.swift
var collectionViewOffset: CGFloat {
set {
appsCollectionView.contentOffset.x = newValue
}
get {
return appsCollectionView.contentOffset.x
}
}

#zahaniza thank You for answer, in addition to Your code
If You are using multiple sections the code will look like
ViewController.swift
var storedOffsets = [IndexPath: CGFloat]()
override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
guard let collectionViewCell = cell as? CategoryCell else { return }
collectionViewCell.collectionViewOffset = storedOffsets[indexPath] ?? 0
}
override func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
guard let collectionViewCell = cell as? CategoryCell else { return }
storedOffsets[indexPath] = collectionViewCell.collectionViewOffset
}

Inner collection views get reused. If you want to keep the scroll position for each horizontal collection view you must keep an array of scroll positions in your UIViewController and update it when inner collection view gets scrolled. Then when reusing outer collection view cells you can reset the scroll position in collectionView(collectionView:, cellForItemAt indexPath:) method.
Your outer collection view population method should look something like this:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! CategoryCell
cell.nameLabel.text = "Horizontal list #\(indexPath.item)"
cell.scrollPosition = this.scrollPositions[indexPath.item]
return cell
}

From the answer of #zahaniza, this code fixes his issue and works well for iPhone X :
var storedOffsets = [Int: CGFloat]()
override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
guard let collectionViewCell = cell as? CategoryCell,
let innerCollectionView = collectionViewCell.collectionView else { return }
// Default collection offset is -adjustedContentInset.left and not 0
let minOffset = -innerCollectionView.adjustedContentInset.left
let maxOffset = innerCollectionView.contentSize.width - innnerCollectionView.frame.width
maxOffset += innerCollectionView.adjustedContentInset.left
if let offset = storedOffsets[indexPath.row] {
innerCollectionView.contentOffset = CGPoint(max(minOffset, miin(maxOffset, offset.x)), 0)
} else {
innerCollectionView.contentOffset = CGPoint(minOffset, 0);
}
collectionViewCell.collectionViewOffset = storedOffsets[indexPath.row] ?? 0
}
override func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
guard let collectionViewCell = cell as? CategoryCell else { return }
storedOffsets[indexPath.row] = collectionViewCell.collectionView.contentOffset
}

Related

Choose cell in collection view to new controller

Very new to code and making a project programmatically. I have my collection view all set up but i cant figure out how to tell a specific cell to navigate to a new collection view or detail view. Very confused and frustrated. can anyone help me or at least point me in the right direction. please dumb it down haha.
this is my main View controller
import UIKit
class HomeController: UICollectionViewController,
UICollectionViewDelegateFlowLayout {
var Legends: [Legend] = {
var select1 = Legend()
select1.thumbnailImageName = "select1thumbnail"
var select2 = Legend()
select2.thumbnailImageName = "select2humbnail"
return[select1, select2]
}()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Choose Selection"
collectionView.backgroundView = UIImageView(image: UIImage(named: "backgroundlogo"))
collectionView?.register(VideoCell.self, forCellWithReuseIdentifier: "cellId")
collectionView.dataSource = self
collectionView.delegate = self
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return Legends.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! VideoCell
cell.legend = Legends[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width:view.frame.height, height: 150)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let select2VC = UIViewController()
navigationController?.navigationBar.tintColor = UIColor.white
navigationController?.pushViewController(select2VC, animated: true)
print("selcected")
}
}
this is the swift file i have for my collection view
import UIKit
class VideoCell: UICollectionViewCell {
var legend: Legend? {
didSet {
thumbnailImageView.image = UIImage(named: (legend?.thumbnailImageName)!)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let thumbnailImageView: UIImageView = {
let imageView = UIImageView()
imageView.backgroundColor = UIColor.darkGray
imageView.image = UIImage(named:"bgcolor")
imageView.contentMode = .scaleAspectFit
imageView.clipsToBounds = true
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
func setupViews() {
addSubview(thumbnailImageView)
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-16-[v0]-16-|", options: NSLayoutConstraint.FormatOptions(), metrics: nil, views: ["v0": thumbnailImageView]))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-1-[v0]-0-|", options: NSLayoutConstraint.FormatOptions(), metrics: nil, views: ["v0": thumbnailImageView]))
}
}
in a 3rd file i have just this bit of code
import UIKit
class Legend: NSObject {
var thumbnailImageName: String?
}
the bottom code of the main view controller does print out a "selected" but it prints it for every cell... I assume each cell will need its own viewController.swift file? then tell that cell to that swift file to show the contents?? thank you for your time.
No you don't need to create its own viewController.swift file for each cell. You just need to create one detail page screen (one DetailViewController.swift) and you need to pass the detail of selected cell with variables declared on detail view controller.
Like I have done HERE in demo project with your code and result will be:
What I have done is I have added detailViewController in storyboard and also added class file. and didSelectItemAt will look like:
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
controller.selectedIndex = indexPath.row //pass selected cell index to next view.
self.navigationController?.pushViewController(controller, animated: true)
}
Here you can pass data with controller.selectedIndex = indexPath.row (Herer I am passing selected index) because selectedIndex is a property of DetailViewController because I have declared it in DetailViewController.swift like
var selectedIndex = 0
And same way you can pass other data as well which is related with selected index.
For more info refer demo project.
If you want check what is sell pressed. You can check what is type cell pressed:
if let cell = tableView.cellForRow(at: indexPath) as? VideoCell {
//check some property and go to new UIViewController or UICollectionViewController
//and transfer data
}

UICollectionView method (collectionView cellForItemAt indexPath ) not called

I am trying to make a custom bar menu using UICollectionView,the problem is that cells not showed method collectionView cellForItemAt indexPath not called.
My code so far:
import UIKit
private let reuseIdentifier = "Cell"
class MenuBar : UIView ,UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout{
lazy var collectionView : UICollectionView = {
let layout = UICollectionViewLayout()
let cv = UICollectionView(frame: .zero , collectionViewLayout : layout)
cv.dataSource = self
cv.delegate = self
cv.backgroundColor = UIColor.white
return cv
}()
override init(frame: CGRect) {
super.init(frame: frame)
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
addSubview(collectionView)
setUpCollectionViewConstraints()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
cell.backgroundColor = UIColor.red
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: frame.height/2, height: frame.height)
}
fileprivate func setUpCollectionViewConstraints() {
collectionView.translatesAutoresizingMaskIntoConstraints = false
let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|[cv]|", options: NSLayoutFormatOptions.alignAllCenterY, metrics: nil, views: ["cv" : collectionView])
let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat:"V:|[cv]|", options: NSLayoutFormatOptions.alignAllCenterY, metrics: nil, views: ["cv" : collectionView])
addConstraints(horizontalConstraints)
addConstraints(verticalConstraints)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Please check :
Your layout should be like below.
You used UICollectionViewLayout.
You have to use UICollectionViewFlowLayout
let layout = UICollectionViewFlowLayout()
It looks like the datasource has something wrong.
Your MenuBar is on a ViewController right?
You may not set collectionView's datasource to MenuBar(Delegate too). You might set collectionView's datasource to a ViewController.

How to expand multiple UICollectionViewCells on tap

I'm trying to build a collectionView that can expand multiple cells after they've been selected/tapped or collapsed when they are deselected, everything works fine when the cells remain on the screen, but once the expanded cells go off screen, I get unexpected behaviour.
For example if I select a cell with IndexPath 0 and then scroll down, tap on cell with IndexPath of 8, scroll back to cell with IndexPath 0 (it's already collapsed), I would tap on it and scroll back to the cell with IndexPath 8 and tap on it again it expands + cell with IndexPath 10 would expand too.
The CollectionView has been implemented programmatically as well the UICollectionViewCell has been subclassed.
ViewController that holds the UICollectionView:
import UIKit
class CollectionViewController: UIViewController {
// MARK: - Properties
fileprivate var collectionView: UICollectionView!
var manipulateIndex: NSIndexPath? {
didSet {
collectionView.reloadItems(at: collectionView.indexPathsForSelectedItems!)
}
}
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 20, left: 10, bottom: 10, right: 10)
collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "Cell")
collectionView.backgroundColor = UIColor.white
self.view.addSubview(collectionView)
}
}
// MARK: - UICollectionViewDataSource
extension CollectionViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 13
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CustomCell
cell.textLabel.text = "\(indexPath.item)"
cell.backgroundColor = UIColor.orange
return cell
}
}
// MARK: - UICollectionViewDelegate
extension CollectionViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
let cell = collectionView.cellForItem(at: indexPath) as! CustomCell
cell.expanded = !cell.expanded
manipulateIndex = indexPath as NSIndexPath
return false
}
}
// MARK: - UICollectionViewDelegateFlowLayout
extension CollectionViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if let cell = collectionView.cellForItem(at: indexPath) as? CustomCell {
if cell.expanded == true {
return CGSize(width: self.view.bounds.width - 20, height: 300)
}
if cell.expanded == false {
return CGSize(width: self.view.bounds.width - 20, height: 120.0)
}
}
return CGSize(width: self.view.bounds.width - 20, height: 120.0)
}
}
And the subclassed custom UICollectionViewCell:
import UIKit
class CustomCell: UICollectionViewCell {
var expanded: Bool = false
var textLabel: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
textLabel = UILabel(frame: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height/3))
textLabel.font = UIFont.systemFont(ofSize: UIFont.smallSystemFontSize)
textLabel.textAlignment = .center
contentView.addSubview(textLabel)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Please help and thank you so much to the amazing person, who can help me out! :)
Try this:
Example 1: Expand only one cell at a time
Note: No need to take expanded bool variable in custom cell
var section:Int?
var preSection:Int?
var expand:Bool = false
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 13
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CustomCell
cell.textLabel.text = "\(indexPath.item)"
cell.backgroundColor = UIColor.orange
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if (self.section != nil) {
self.preSection = self.section
}
self.section = indexPath.row
if self.preSection == self.section {
self.preSection = nil
self.section = nil
}else if (self.preSection != nil) {
self.expand = false
}
self.expand = !self.expand
self.collectionView.reloadItems(at: collectionView.indexPathsForSelectedItems!)
}
}
// MARK: - UICollectionViewDelegateFlowLayout
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if self.expand, let row = self.section, row == indexPath.row {
return CGSize(width: self.view.bounds.width - 20, height: 300)
}else{
return CGSize(width: self.view.bounds.width - 20, height: 120.0)
}
}
}
Example 2:
Expand multiple cell
import UIKit
class ViewController: UIViewController {
// MARK: - Properties
fileprivate var collectionView: UICollectionView!
var expandSection = [Bool]()
var items = [String]()
override func viewDidLoad() {
super.viewDidLoad()
self.items = ["A","B","C","D","E","F","G","H","J","K"]
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 20, left: 10, bottom: 10, right: 10)
collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "Cell")
collectionView.backgroundColor = UIColor.white
self.expandSection = [Bool](repeating: false, count: self.items.count)
self.view.addSubview(collectionView)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CustomCell
cell.textLabel.text = self.items[indexPath.row]
cell.backgroundColor = UIColor.orange
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.expandSection[indexPath.row] = !self.expandSection[indexPath.row]
self.collectionView.reloadItems(at: collectionView.indexPathsForSelectedItems!)
}
}
// MARK: - UICollectionViewDelegateFlowLayout
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if self.expandSection[indexPath.row] {
return CGSize(width: self.view.bounds.width - 20, height: 300)
}else{
return CGSize(width: self.view.bounds.width - 20, height: 120.0)
}
}
}
UICollectionView will reuse your cells for multiple objects in your data model. You can't control which cells get reused when during reloadItems You should not assume that the expanded state in a given cell corresponds to the state of your data model. Instead, you should be holding onto the expanded state somehow in your data model and re-setting that in every call to cellForItemAt.
In other words, hold your state in your model and set the cell state in cellForItemAt, don't hold it in the cells themselves.

UICollectionView Multiple Sections and Headers

I am struggling trying to do multiple sections in my collection view with a header for each section. I don't know Obj-C and I've found a good amount of tutorials for it, but haven't been able to figure out how to convert it into Swift.
All my data is static so all I need is some sort of array or dictionary that I can use to create the multiple sections. I already have a collection view with 1 section working, so if you have any insight or code for multiple sections that'd be helpful.
I know how to set multiple sections using
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return sectionData.count
}
I think the main thing I need help with is implementing this func
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { }
and setting up the data!
UICollectionView and UITableView are almost exactly the same, so if you know how to do multiple sections in a UITableView in Swift, your help is also appreciated
The cellForItemAtIndexPath function handles populating each section with cells, it does not handle sections or supplementaryViews, and therefore is not the main thing you need help with when it comes to creating section headers.
the method you need to implement is viewForSupplementaryElementOfKind. Its signature is:
func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {}
Assuming that your collectionView is working correctly for 1 section (you have properly filled out the body of your cellForItemAtIndexPath and your sectionData array properly reflects the number of sections you want to display), you should be able to implement section headers using the following pointers:
Along with cells, UICollectionView also supports "supplementary" view objects, typically used for headers or footers. These Supplementary Views act very similarly to UICollectionViewCell objects. In the same way that cellForItemAtIndexPath handles cells, The viewForSupplementaryElementOfKind function handles supplementary views.
To implement it, you will need to first prepare your ViewController to do so. First edit your layout object to reflect an appropriate header size, that each header will adhere to:
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.headerReferenceSize = CGSize(width: self.view.frame.size.width, height: 30)
NOTE: I am using a UICollectionViewFlowLayout
Next, if you haven't already done so, create a SectionHeader class that defines each section header object, so you can then register that class with your collectionView object like so:
collectionView!.registerClass(SectionHeaderView.self, forSupplementaryViewOfKind:UICollectionElementKindSectionHeader, withReuseIdentifier: "SectionHeaderView");
Here, the first and third argument passed in are the same as a UICollectionViewCell Class registration, the first argument in this method is the reference to the section header class you created. The third is the reuse identifier for the Supplementary View.
The second argument is specific to Supplementary Views, this sets the kind of the SupplementaryView, which in this case is a header, the constant string provided by the UICollectionViewFlowLayout class UICollectionElementKindSectionHeader is used for it. If you noticed the parameters on the viewForSupplementaryElementOfKind, this kind is later passed in as the kind: String parameter.
Fill in the body of your viewForSupplementaryElementOfKind the same way you would for a cellForItemAtIndexPath function-- Using the dequeueReusableSupplementaryViewOfKind method to create a SectionHeader object, then set any attributes as necessary (labels, colors, etc.) and finally return the header object.
Hope this helps!!
Reference points:
https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UICollectionViewDataSource_protocol/index.html#//apple_ref/occ/intfm/UICollectionViewDataSource/
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UICollectionViewFlowLayout_class/index.html#//apple_ref/c/data/UICollectionElementKindSectionHeade
Define your UICollectionViewCell which will be your Header view of kind UICollectionElementKindSectionHeader - In my case I have two headers - OfferHeaderCell and APRHeaderCell defined as below:
verticalCollectionView.register(UINib(nibName: "OfferHeaderCell", bundle: nil), forSupplementaryViewOfKind:UICollectionElementKindSectionHeader, withReuseIdentifier: "OfferHeaderCell")
verticalCollectionView.register(UINib(nibName: "APRHeaderCell", bundle: nil), forSupplementaryViewOfKind:UICollectionElementKindSectionHeader, withReuseIdentifier: "APRHeaderCell")
Go ahead and return a header for each section and then set the size of the section header to have a size of zero in this UICollectionViewDelegateFlowLayout function
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
if(section==0) {
return CGSize.zero
} else if (section==1) {
return CGSize(width:collectionView.frame.size.width, height:133)
} else {
return CGSize(width:collectionView.frame.size.width, height:100)
}
}
Important to define the viewForSupplementaryElementOfKind for two different sections as below:
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
var reusableview = UICollectionReusableView()
if (kind == UICollectionElementKindSectionHeader) {
let section = indexPath.section
switch (section) {
case 1:
let firstheader: OfferHeaderCell = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "OfferHeaderCell", for: indexPath) as! OfferHeaderCell
reusableview = firstheader
case 2:
let secondHeader: APRHeaderCell = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "APRHeaderCell", for: indexPath) as! APRHeaderCell
reusableview = secondHeader
default:
return reusableview
}
}
return reusableview
}
And lastly the Datasource,
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if (section==2) {
return 2
}
return 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = verticalCollectionView.dequeueReusableCell(withReuseIdentifier: "ReviseOfferCell", for: indexPath)
cell.backgroundColor = UIColor.white
return cell
}
Note: Don't forgot to add UICollectionFlowLayout as below:
// MARK: UICollectionViewDelegateFlowLayout
extension MakeAnOfferController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if indexPath.item == 0 {
return CGSize(width: self.view.frame.size.width, height: 626.0)
}
return CGSize()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
if(section==0) {
return CGSize.zero
} else if (section==1) {
return CGSize(width:collectionView.frame.size.width, height:133)
} else {
return CGSize(width:collectionView.frame.size.width, height:100)
}
}
}
Here is the code that worked for me
create the header cell. To do which i created a custom cell class and a nib to do the customization of the cell in the graphic editor
In viewDidLoad add the following
self.collectionView?.registerNib(UINib(nibName: "KlosetCollectionHeaderViewCell", bundle: nil), forSupplementaryViewOfKind:UICollectionElementKindSectionHeader, withReuseIdentifier: "HeaderCell")
Then you add the delegate function
override func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> KlosetCollectionHeaderViewCell {
let headerCell = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: "HeaderCell", forIndexPath: indexPath) as? KlosetCollectionHeaderViewCell
return headerCell!
}
This will put the HeaderCell in the SectionView of the PFCollectionView
The controls that show in the cell you add them to the xib file as well as the outlets and actions
Here is the code to achieve UICollection multiple sections made programmatically using SnapKit
ViewController
import SnapKit
import UIKit
class SelectIconViewController: GenericViewController<SelectIconView>, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
weak var delegate: SpaceAddViewController?
struct Section {
var sectionName : String
var rowData : [String]
}
var sections : [Section]!
init(delegate: SpaceAddViewController) {
self.delegate = delegate
super.init()
}
public required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
contentView.closeButton.addTarget(self, action: #selector(self.back), for: .touchUpInside)
self.sections = [
Section(sectionName: "SPACES", rowData: ["Air Conditioner", "Apple HomePod"]),
Section(sectionName: "HOME APPLIANCES", rowData: ["Ceiling Fan", "Fan", "Desk Lamp", "Iron", "PC on Desk", "Plug", "Power Strip", "Lorem", "Lorem", "Lorem", "Lorem"]),
]
self.contentView.collectionView.dataSource = self
self.contentView.collectionView.delegate = self
self.contentView.collectionView.register(SelectIconHeaderViewCell.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: SelectIconHeaderViewCell.reuseId)
self.contentView.collectionView.register(SelectIconViewCell.self, forCellWithReuseIdentifier: SelectIconViewCell.reuseId)
}
#objc func back() {
self.dismiss(animated: true, completion: nil)
}
#objc func dismissKeyboard() {
view.endEditing(true)
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return self.sections.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.sections[section].rowData.count
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: getTotalSpacing(), height: getTotalSpacing())
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
let screenSize = UIScreen.main.bounds
let screenWidth = screenSize.width-40
return CGSize(width: screenWidth-80, height: 50)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
// MARK: Cells
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = self.contentView.collectionView.dequeueReusableCell(withReuseIdentifier: SelectIconViewCell.reuseId, for: indexPath as IndexPath) as! SelectIconViewCell
cell.initializeUI()
cell.createConstraints()
cell.setValues(iconName: "", label: self.sections[indexPath.section].rowData[indexPath.row])
return cell
}
// MARK: Header
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionView.elementKindSectionHeader:
let cell = self.contentView.collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: SelectIconHeaderViewCell.reuseId, for: indexPath) as! SelectIconHeaderViewCell
cell.initializeUI()
cell.createConstraints()
cell.setTitle(title: self.sections[indexPath.section].sectionName)
return cell
default: fatalError("Unexpected element kind")
}
}
func getTotalSpacing() -> CGFloat {
let screenSize = UIScreen.main.bounds
let screenWidth = screenSize.width
let numberOfItemsPerRow:CGFloat = 3
let spacingBetweenCells:CGFloat = 0
let sideSpacing:CGFloat = 20
return (screenWidth-(2 * sideSpacing) - ((numberOfItemsPerRow - 1) * spacingBetweenCells))/numberOfItemsPerRow
}
}
The View:
import UIKit
import SnapKit
class SelectIconView: GenericView {
private let contentView = UIView(frame: .zero)
private (set) var closeButton = UIButton(type: .system)
internal var collectionView: UICollectionView!
internal override func initializeUI() {
self.backgroundColor = Theme.Color.white
self.addSubview(contentView)
contentView.addSubview(closeButton)
if let image = UIImage(named: "icon_close") {
image.withRenderingMode(.alwaysTemplate)
closeButton.setImage(image, for: .normal)
closeButton.tintColor = Theme.Color.text
}
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.minimumInteritemSpacing = 0
collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
contentView.addSubview(collectionView)
collectionView.backgroundColor = Theme.Color.background
}
internal override func createConstraints() {
contentView.snp.makeConstraints { (make) in
make.top.equalTo(safeAreaLayoutGuide.snp.top).priority(750)
make.left.right.equalTo(self).priority(1000)
make.bottom.equalTo(safeAreaLayoutGuide.snp.bottom)
}
closeButton.snp.makeConstraints { make in
make.right.equalTo(safeAreaLayoutGuide.snp.right).offset(-10)
make.top.equalTo(contentView.snp.top).offset(10)
make.height.equalTo(40)
make.width.equalTo(40)
}
collectionView.snp.makeConstraints { make in
make.top.equalTo(closeButton.snp.bottom).offset(20)
make.left.equalTo(safeAreaLayoutGuide.snp.left)
make.right.equalTo(safeAreaLayoutGuide.snp.right)
make.bottom.equalTo(contentView.snp.bottom)
}
}
}
The customized section Header
import UIKit
class SelectIconHeaderViewCell: UICollectionViewCell {
internal let mainView = UIView()
internal var title = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func initializeUI() {
self.backgroundColor = UIColor.clear
self.addSubview(mainView)
mainView.backgroundColor = UIColor.clear
mainView.addSubview(title)
title.text = "Pick nameA"
title.font = Theme.Font.body()
title.textAlignment = .left
title.textColor = Theme.Color.text
title.numberOfLines = 1
}
internal func createConstraints() {
mainView.snp.makeConstraints { (make) in
make.edges.equalTo(self)
}
title.snp.makeConstraints { (make) in
make.centerY.equalTo(mainView.snp.centerY)
make.leading.equalTo(mainView).offset(20)
make.trailing.equalTo(mainView).offset(-20)
}
}
func setTitle(title: String) {
self.title.text = title
}
static var reuseId: String {
return NSStringFromClass(self)
}
}
And the cell:
import UIKit
class SelectIconViewCell: UICollectionViewCell {
internal let mainView = UIView()
internal var iconImage = UIImageView()
internal var label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func initializeUI() {
self.backgroundColor = UIColor.clear
self.addSubview(mainView)
mainView.backgroundColor = UIColor.clear
mainView.layer.masksToBounds = true
mainView.layer.borderColor = Theme.Color.backgroundCell.cgColor
mainView.layer.borderWidth = 1.0
mainView.addSubview(iconImage)
iconImage.image = UIImage(named: "icons8-air-conditioner-100")
mainView.addSubview(label)
label.font = Theme.Font.footnote()
label.textAlignment = .center
label.textColor = Theme.Color.textInfo
label.numberOfLines = 1
}
internal func createConstraints() {
mainView.snp.makeConstraints { (make) in
make.edges.equalTo(self)
}
iconImage.snp.makeConstraints { (make) in
make.center.equalTo(mainView.snp.center)
make.width.height.equalTo(20)
}
label.snp.makeConstraints { (make) in
make.top.equalTo(iconImage.snp.bottom).offset(6)
make.leading.equalTo(mainView).offset(5)
make.trailing.equalTo(mainView).offset(-5)
}
}
func setValues(iconName: String, label: String) {
//self.iconImage.image = UIImage(named: iconName)
self.label.text = label
}
static var reuseId: String {
return NSStringFromClass(self)
}
}
After creating and registering custom header (and/or footers), you can easily specify different header (or footers for that matter) for different section. Here's an example:
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionElementKindSectionHeader:
let section = indexPath.section
switch section {
case 0:
let userHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: userHeaderReuseIdentifier, for: indexPath) as! UserHeader
return userHeader
default:
let postHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: postHeaderReuseIdentifier, for: indexPath) as! PostHeader
return postHeader
}
case UICollectionElementKindSectionFooter:
let userFooter = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: userFooterReuseIdentifier, for: indexPath) as! UserFooter
return userFooter
default:
return UICollectionReusableView()
}
}
Make sure to specify correct number of sections, too:
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 2
}
Worked solution for Swift-3
i)Create Custom Cell && corresponding xib
class SectionHeaderView: UICollectionViewCell {
static let kReuseIdentifier = "SectionHeaderView"
#IBOutlet weak var invitationsSectionHeader: UILabel!
#IBOutlet weak var numberOfPerson: UILabel!
}
ii)Register Custom Collection View Cell for HeaderView
self.collectionView.register(UINib(nibName: SectionHeaderView.kReuseIdentifier, bundle: nil), forSupplementaryViewOfKind:UICollectionElementKindSectionHeader, withReuseIdentifier: SectionHeaderView.kReuseIdentifier)
iii)Call delegate function to render Custom Header View.
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionElementKindSectionHeader:
let headerView: SectionHeaderView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: SectionHeaderView.kReuseIdentifier, for: indexPath) as! SectionHeaderView
return headerView
default:
return UICollectionReusableView()
}
}
iv)Mention Height of the Custom Header View
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width:collectionView.frame.size.width, height:30)
}
#Tarun's answer worked a treat for me; I was missing collectionView(_:layout:referenceSizeForHeaderInSection:), which I needed since sometimes the data to be shown would be sorted and sometimes not.
In addition, pinning the section header to the top of the screen (as in the table view Apple's Address Book app) was accomplished by adding the following to viewDidLoad() in the UICollectionViewController:
if let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.sectionHeadersPinToVisibleBounds = true
}

UICollectionView in a UICollectionViewCell

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{
}

Resources