UICollectionView hide/prevent cell scrolling behind Sticky Header - ios

I have a collection view with sticky headers
flowLayout.sectionHeadersPinToVisibleBounds = true
My issue is that the top half of my header is translucent and when I scroll my cell up I can see the cell scrolling behind the header.
I would like to hide the part of the cell view behind the header. In my image attached I do not want to see the green when it is behind the red. The rest of the behavior I want to keep as is.
The reason I need this is I have a wallpaper image at the very back which I need to be shown
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView.alwaysBounceVertical = true
collectionView.register(UINib.init(nibName: EXAMPLE_CELL_REUSE_ID, bundle: nil), forCellWithReuseIdentifier: EXAMPLE_CELL_REUSE_ID)
collectionView.register(UINib.init(nibName: EXAMPLE_HEADER_REUSE_ID, bundle: nil), forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: EXAMPLE_HEADER_REUSE_ID)
if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.headerReferenceSize = CGSize(width: 400, height: 100)
flowLayout.sectionHeadersPinToVisibleBounds = true
}
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return sections.count;
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1 //self.sections[section].1;
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let exampleCell = collectionView.dequeueReusableCell(withReuseIdentifier: EXAMPLE_CELL_REUSE_ID, for: indexPath) as! MyCellCollectionViewCell;
exampleCell.headerLabel.text = "Cell"
exampleCell.backgroundColor = UIColor.green
return exampleCell;
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if kind == UICollectionElementKindSectionHeader {
if let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: EXAMPLE_HEADER_REUSE_ID, for: indexPath) as? ExampleHeader {
// header.backgroundColor = UIColor(red: 1.0, green: 0, blue: 0, alpha: 0.5)
return header
} else {
return UICollectionReusableView()
}
}
return UICollectionReusableView()
}
I think the question here may be similar but there are no responses and it is not quite clear if it is the same issue.
Transparent sticky header ui collectionview don't show cells underneath

This worked for me:
let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
layout.sectionHeadersPinToVisibleBounds = true
layout.sectionInsetReference = .fromSafeArea
view.backgroundColor = .white
collectionView.anchor(top: view.safeAreaLayoutGuide.topAnchor, leading:
view.leadingAnchor, bottom: view.bottomAnchor, trailing: view.trailingAnchor)

You need to apply mask for all cells that located under header view. Use on scrollViewDidScroll
Tutorial: https://medium.com/#peteliev/layer-masking-for-beginners-c18a0a10743

I have created a very simple setup like yours and it is working fine.
class ViewController: UIViewController, UICollectionViewDataSource
{
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad()
{
super.viewDidLoad()
self.collectionView.alwaysBounceVertical = true
if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout
{
flowLayout.headerReferenceSize = CGSize(width: 400, height: 100)
flowLayout.sectionHeadersPinToVisibleBounds = true
}
}
func numberOfSections(in collectionView: UICollectionView) -> Int
{
return 10
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
return collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView
{
if kind == UICollectionElementKindSectionHeader
{
return collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "rview", for: indexPath)
}
return UICollectionReusableView()
}
}

Related

insert/ delete sections in cells UICollectionView

Good afternoon,
I am trying to create expandable/ collapsible cells in a UICollectionViewController. The cells expand and collapse correctly. However, the information each cell holds stays present on the screen in the background. I will show pictures to explain. I am trying to delete the information from the view if the user collapses the cell, and insert it back if the user expands that cell.
The images below express the process of the cell starting in a collapsed state, expanding that cell, them collapsing that cell again. (note: this happens to every cell not just the first cell I clicked.
the first image is when the view loads and the cells are in the original collapsed state.
this second image shows when the user taps on the cell to expand it.
the third image shows when the user tries to collapse the cell.
the code that I use to expand and collapse the cells are below
fileprivate let cellId = "cellId"
fileprivate let headerId = "headerId"
fileprivate let profID = "profID"
private let headerIdentifier = "userProfileHeader"
private let sectionIdentifier = "sectionHeader"
var section:Int?
var expandSection = [Bool]()
var items = [String]()
fileprivate func setupCollectionView() {
collectionView.backgroundColor = .white
collectionView.contentInsetAdjustmentBehavior = .never
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId)
self.expandSection = [Bool](repeating: false, count: self.items.count)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(userProfileHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerIdentifier)
collectionView.register(sectionHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: sectionIdentifier)
}
fileprivate func registerCollectionView() {
collectionView.register(profCell.self, forCellWithReuseIdentifier: "profCell")
self.collectionView.register(userProfileHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerIdentifier)
self.collectionView.register(sectionHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: sectionIdentifier)
}
var headerView: userProfileHeader?
var sectionView: sectionHeader?
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if kind == UICollectionView.elementKindSectionHeader {
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerIdentifier, for: indexPath) as? userProfileHeader
if let user = self.user {
headerView?.myUser = user
} else if let userToLoad = self.userToLoad {
headerView?.myUser = userToLoad
}
return headerView!
} else {
let sectionViews = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: sectionIdentifier, for: indexPath) as? sectionHeader
let categories = ["Skills, Preferences", "Bio", "Reviews"]
sectionViews!.headerLabel.text = categories[indexPath.section]
sectionViews!.backgroundColor = .lightGray
return sectionViews!
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return .init(width: view.frame.width, height: 340)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.expandSection[indexPath.row] = !self.expandSection[indexPath.row]
self.collectionView.reloadItems(at: collectionView.indexPathsForSelectedItems!)
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "profCell", for: indexPath) as! profCell
if indexPath.row == 0 {
cell.educationView.text = user.education
cell.skillsView.text = user.skills
cell.preferencesView.text = "wassup bitches"
} else if indexPath.row == 1 {
cell.bioLabel.text = user.bio
} else if indexPath.row == 2 {
cell.labels.text = "Reviews"
}
}
return cell
}
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: 80)
}
}
Instead of trying to insert and remove items inside sizeForItemAt(), call reloadSections(_ sections: IndexSet) for the sections that get expanded or contracted. Then implement numberOfItems(inSection section: Int) to return 0 when contracted or items.count when expanded. Use sections to hold the top level items and cells to hold the collapsible sub-items.

collectionView didSelectItemAt superview not selecting the correct Object

I have a list of sensors in a collectionView inside a UIView class called MyCropView where I show some data about that crop.
Im trying to change the value of some sensors like the icon and the background color on each click.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let myCropCard = collectionView.superview as! MyCropCard
let sensor = myCropCard.sensors[indexPath.row]
let cell = myCropCard.superview
//Change the color and icon of the sensors
if let cell = collectionView.cellForItem(at: indexPath) as? MyCollectionViewCell {
//Previous cell in grey
if !self.lastSelectedIndexPath.isEmpty {
let lastCell = collectionView.cellForItem(at: self.lastSelectedIndexPath) as? MyCollectionViewCell
lastCell?.imageView.backgroundColor = UIColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1.0)
lastCell?.imageView.image = UIImage(named: self.lastSelectedImageName )
}
//Makes sensor green onSelect
cell.imageView.backgroundColor = UIColor(red: 0.882, green: 0.95, blue: 0.882, alpha: 1.0)
cell.imageView.image = UIImage(named: sensor.getType() )
self.lastSelectedIndexPath = indexPath
self.lastSelectedImageName = String(format: "%#_deactivated", sensor.getType())
}
//Show all the alerts of each sensor
for alert:Alert in sensor.getAlerts() {
let viewSensor = cell?.viewWithTag(300) as! SensorView
viewSensor.lblSensor.text = sensor.getTipo()
viewSensor.lblBotLog.text = alerta.getNombreDispositivo()
viewSensor.lblMin.text = String(format: "MIN %.0f", alert.getMin())
viewSensor.lblMax.text = String(format: "MAX %.0f", alert.getMax())
viewSensor.btnDel.tag = Int(alert.getId() + 1000)
viewSensor.btnDel.addTarget(
self,
action: #selector( viewSensor.deleteAlert(_:) ),
for: .touchUpInside
)
viewSensor.isHidden = false
}
}
That collectionView is the list of sensors we have. It works fine with 1st MyCropCard, But doesn't for multiple MyCropCards. Superview takes the first cropCard of them all, not the collectionView's parent.
I would like to know how can I correctly have the parent of the selected collectionView?
Your code is cluttered with unneccessary logic-related codes, which are probably unrelated to the issue.
Also, extensive use of tags are making the code unscalable and hard to maintain, so it probably needs a heavy refactoring.
The following code is basically a MWE for what you want to achieve (If I misunderstood your issue, let me know).
To explain the code, this is a custom tableViewCell:
class TableViewCell: UITableViewCell {
var collectionView: UICollectionView? {
didSet {
guard let collectionView = collectionView else { return }
contentView.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = .white
setupCollectionViewLayout(collectionView)
}
}
private func setupCollectionViewLayout(_ collectionView: UICollectionView) {
NSLayoutConstraint.activate([
collectionView.leadingAnchor
.constraint(equalTo: contentView.leadingAnchor),
collectionView.trailingAnchor
.constraint(equalTo: contentView.trailingAnchor),
collectionView.topAnchor
.constraint(equalTo: contentView.topAnchor),
collectionView.bottomAnchor
.constraint(equalTo: contentView.bottomAnchor),
])
}
}
setupCollectionViewLayout simply enables AutoLayout. collectionView will be later added when a tableViewCell instance is dequeued.
class CollectionViewCell: UICollectionViewCell {}
class ViewController: UIViewController {
private var lastSelectedCollectionViewCell: CollectionViewCell?
private lazy var tableView: UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.delegate = self
tableView.dataSource = self
tableView.register(TableViewCell.self, forCellReuseIdentifier: "cell")
return tableView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(tableView)
setupTableViewLayout(tableView)
}
private func setupTableViewLayout(_ tableView: UITableView) {
NSLayoutConstraint.activate([
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
}
tableView is added as the view is loaded, and AutoLayout constraints are added by setupTableViewLayout.
Note that you keep a copy of the last index path, but I think it is better and simpler to keep a reference to the cell itself.
In UITableViewDataSource extension:
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(
withIdentifier: "cell",
for: indexPath) as! TableViewCell
guard cell.collectionView == nil else { return cell }
let collectionView = UICollectionView(
frame: .zero,
collectionViewLayout: UICollectionViewFlowLayout())
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: "cell")
cell.collectionView = collectionView
return cell
}
}
extension ViewController: UITableViewDelegate {}
I add a collectionView only if it doesn't already exist in a tableViewCell.
collectionView assigns dataSource and delegate as self, though it would be better if there is an extra modelController object.
UITableViewDelegate is empty.
Now extensions for the collectionViews:
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.backgroundColor = .gray
return cell
}
}
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
lastSelectedCollectionViewCell?.backgroundColor = .gray
if let cell = collectionView.cellForItem(at: indexPath) as? CollectionViewCell {
cell.backgroundColor = .yellow
lastSelectedCollectionViewCell = cell
}
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 20, height: 20)
}
}
The important part is collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath), and the others are just boilerplate code.
When a collectionViewCell is selected, the self.lastSelectedCollectionViewCell's color turns back to gray, and the newly selected cell is assigned to lastSelectedCollectionViewCell, after its backgroundColor changed.
The following is the entire code you can run in Playground:
import UIKit
import PlaygroundSupport
class TableViewCell: UITableViewCell {
var collectionView: UICollectionView? {
didSet {
guard let collectionView = collectionView else { return }
contentView.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = .white
setupCollectionViewLayout(collectionView)
}
}
private func setupCollectionViewLayout(_ collectionView: UICollectionView) {
NSLayoutConstraint.activate([
collectionView.leadingAnchor
.constraint(equalTo: contentView.leadingAnchor),
collectionView.trailingAnchor
.constraint(equalTo: contentView.trailingAnchor),
collectionView.topAnchor
.constraint(equalTo: contentView.topAnchor),
collectionView.bottomAnchor
.constraint(equalTo: contentView.bottomAnchor),
])
}
}
class CollectionViewCell: UICollectionViewCell {}
class ViewController: UIViewController {
private var lastSelectedCollectionViewCell: CollectionViewCell?
private lazy var tableView: UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.delegate = self
tableView.dataSource = self
tableView.register(TableViewCell.self, forCellReuseIdentifier: "cell")
return tableView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(tableView)
setupTableViewLayout(tableView)
}
private func setupTableViewLayout(_ tableView: UITableView) {
NSLayoutConstraint.activate([
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(
withIdentifier: "cell",
for: indexPath) as! TableViewCell
guard cell.collectionView == nil else { return cell }
let collectionView = UICollectionView(
frame: .zero,
collectionViewLayout: UICollectionViewFlowLayout())
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: "cell")
cell.collectionView = collectionView
return cell
}
}
extension ViewController: UITableViewDelegate {}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.backgroundColor = .gray
return cell
}
}
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
lastSelectedCollectionViewCell?.backgroundColor = .gray
if let cell = collectionView.cellForItem(at: indexPath) as? CollectionViewCell {
cell.backgroundColor = .yellow
lastSelectedCollectionViewCell = cell
}
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 20, height: 20)
}
}
PlaygroundPage.current.liveView = ViewController()

UICollection View Custom Header not showing Swift 4

Please help, I've been trying to figure out why my code doesnt work. What I want to do is to have a custom header for my collection view but it seems that the class that I registered doesn't seem to trigger.
It doesn't even show the red box. It just shows a cyan blank background
What am I doing wrong?
Below are my codes:
class VideoDetailController : UICollectionViewController, UICollectionViewDelegateFlowLayout {
private let headerId = "headerId"
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = UIColor.cyan
collectionView?.register(VideoDetailHeader.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: headerId)
navigationItem.title = "Hello!"
}
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionViewCell {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: headerId, for: indexPath) as! VideoDetailHeader
return header
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: collectionView.frame.width,height:170)
}
}
class VideoDetailHeader: BaseCell {
override func setupViews() {
print("Setting up...")
super.setupViews()
backgroundColor = UIColor.red
}
}
BaseCell is UICollectionViewCell
It seems that the above codes work:
The only issue is that when I call this view controller, I used:
let layout = UICollectionViewLayout()
instead of :
let layout = UICollectionViewFlowLayout()

How to implement content view above UICollectionView

I am trying to get the UICollectionView, and UIView above it, to scroll together. I've done a couple of diagrams (before and after scrolling) to show what I'm trying to achieve.
Before scrolling
After scrolling
Currently, I have only been able to get the UICollectionView to scroll within its own section of the screen. I've also tried wrapping both the UIView and UICollectionView into a UIScrollView, although then the UICollectionView doesn't appear on screen.
What's the best way to implement this layout?
Here is a collection view with a section header that scrolls when the collection view scrolls.. If you need a parallax header (stretchy header effect), then there are tons of tutorials online for that..
import UIKit
class Header : UICollectionReusableView {
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.green
}
#available(iOS, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
private var flowLayout: UICollectionViewFlowLayout!
private var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.white
self.flowLayout = UICollectionViewFlowLayout()
self.flowLayout.scrollDirection = .vertical;
self.flowLayout.minimumInteritemSpacing = 15;
self.flowLayout.minimumLineSpacing = 15;
self.collectionView = UICollectionView(frame: .zero, collectionViewLayout: self.flowLayout)
self.collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "default")
self.collectionView.register(UICollectionReusableView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "default")
self.collectionView.register(Header.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "header")
self.collectionView.backgroundColor = UIColor(red: 0.0, green: 0.0, blue: 1.0, alpha: 0.5)
self.collectionView.dataSource = self
self.collectionView.delegate = self
self.collectionView.reloadData()
self.view.addSubview(self.collectionView)
self.collectionView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
self.collectionView.leftAnchor.constraint(equalTo: self.view.leftAnchor),
self.collectionView.rightAnchor.constraint(equalTo: self.view.rightAnchor),
self.collectionView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor),
self.collectionView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor)
])
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "default", for: indexPath)
cell.contentView.backgroundColor = UIColor.white
cell.contentView.layer.borderColor = UIColor.red.cgColor
cell.contentView.layer.borderWidth = 1.0
return cell
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if indexPath.section == 0 && kind == UICollectionElementKindSectionHeader {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "header", for: indexPath)
return header
}
return collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "default", for: indexPath)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
if section == 0 {
return CGSize(width: collectionView.bounds.width, height: 200.0)
}
return .zero
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let layout = collectionViewLayout as! UICollectionViewFlowLayout
let insets = self.collectionView(collectionView, layout: layout, insetForSectionAt: indexPath.section)
var width = collectionView.bounds.width - 15.0
width -= insets.left + insets.right
return CGSize(width: floor(width), height: 100.0)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0)
}
}

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
}

Resources