Make sure only 1 cell has an active state in a UICollectionView - ios

I have an UICollectionView in which I want only want 1 cell to be active. With active I mean: the last cell that has been clicked (or the very first cell when to collection view lays out). When a user clicks a non-active cell, I want to reset the old active cell to a non-active state. I am having trouble doing this. This is because visibleCells, a property of collection view, only returns the cells on screen but not the cells in memory. This is my current way to locate an active cell and reset the state to non active.
This scenario can happen, causing multiple active cells: A user scroll slightly down so that the current active cell is not visible anymore, taps on a random cell and scroll up. The problem is that the old active cell stays in memory, although it is not visible: cellForItemAt(_:) does not gets called for that cell. Bad news is that visibleCells also do not find the old active cell. How can I find it? The function willDisplay cell also does not work.
An example project can be cloned directly into xCode: https://github.com/Jasperav/CollectionViewActiveIndex.
This is the code in the example project:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var collectionView: CollectionView!
static var activeIndex = 0
override func viewDidLoad() {
super.viewDidLoad()
collectionView.go()
}
}
class Cell: UICollectionViewCell {
#IBOutlet weak var button: MyButton!
}
class CollectionView: UICollectionView, UICollectionViewDelegate, UICollectionViewDataSource {
func go() {
delegate = self
dataSource = self
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 500
}
internal func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! Cell
if indexPath.row == ViewController.activeIndex {
cell.button.setTitle("active", for: .normal)
} else {
cell.button.setTitle("not active", for: .normal)
}
cell.button.addTarget(self, action: #selector(touchUpInside(_:)), for: .touchUpInside)
return cell
}
#objc private func touchUpInside(_ sender: UIButton){
let hitPoint = sender.convert(CGPoint.zero, to: self)
guard let indexPath = indexPathForItem(at: hitPoint), let cell = cellForItem(at: indexPath) as? Cell else { return }
// This is the problem. It does not finds the current active cell
// if it is just out of bounds. Because it is in memory, cellForItemAt: does not gets called
if let oldCell = (visibleCells as! [Cell]).first(where: { $0.button.titleLabel!.text == "active" }) {
oldCell.button.setTitle("not active", for: .normal)
}
cell.button.setTitle("active", for: .normal)
ViewController.activeIndex = indexPath.row
}
}

To recover from this glitch you can try in cellForRowAt
cell.button.tag = indexPath.row
when the button is clicked set
ViewController.activeIndex = sender.tag
self.reloadData()

You can use the isSelected property of the UIColectionViewCell. You can set an active layout to your cell if it is selected. The selection mechanism is implemented by default in the UIColectionViewCell. If you want to select/activate more than one cell you can set the property allowsMultipleSelection to true.
Basically this approach will look like this:
class ViewController: UIViewController {
#IBOutlet weak var collectionView: CollectionView!
override func viewDidLoad() {
super.viewDidLoad()
collectionView.go()
}
func activeIndex()->Int?{
if let selectedItems = self.collectionView.indexPathsForSelectedItems {
if selectedItems.count > 0{
return selectedItems[0].row
}
}
return nil
}
}
class Cell: UICollectionViewCell {
#IBOutlet weak var myLabel: UILabel!
override var isSelected: Bool{
didSet{
if self.isSelected
{
myLabel.text = "active"
}
else
{
myLabel.text = "not active"
}
}
}
}
class CollectionView: UICollectionView, UICollectionViewDelegate, UICollectionViewDataSource {
func go() {
delegate = self
dataSource = self
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 500
}
internal func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! Cell
return cell
}
}

Related

UICollectionViewCell did select item not working

I have an issue with a collectionView inside a TableVieCell. When i tap on a collectionCell, didSelectItemAt doesn´t get called. I have a button in the collectionCell so i tried to disable the user interaction and enable the contentView userInteraction but it didn't work.
The red rectangle is the tableCell and the blue rectangle is the collecionView insede the table view cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "MedCellWithCollection") as? MedCellWithCollection{
let backgroundView = UIView()
backgroundView.backgroundColor = UIColor.white.withAlphaComponent(0.0)
cell.selectedBackgroundView = backgroundView
cell.setMedName(name: self.medCatalog[indexPath.row].nombre, uso: self.medCatalog[indexPath.row].uso , array: self.medCatalog[indexPath.row].enfermedades[0].aplicaciones , index: indexPath.row)
cell.layoutIfNeeded()
cell.layoutSubviews()
cell.setNeedsUpdateConstraints()
cell.updateConstraintsIfNeeded()
return cell
}
}
return UITableViewCell()
}
The tableCell
class MedCellWithCollection: UITableViewCell {
//Outlets
#IBOutlet weak var medText: UILabel!
#IBOutlet weak var uso: UILabel!
#IBOutlet weak var arrowIcon: UIImageView!
#IBOutlet weak var CollectionView: UICollectionView!
//Variables
var dosesType:[Aplicacion]?
override func awakeFromNib() {
super.awakeFromNib()
self.collectionViewSetUp()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func setMedName(name: String, uso: String, array: [Aplicacion], index: Int){
self.medText.text = name
self.uso.text = uso
self.dosesType = array
self.CollectionView.reloadData()
}
}
extension MedCellWithCollection: UICollectionViewDataSource, UICollectionViewDelegate{
func collectionViewSetUp(){
self.CollectionView.delegate = self
self.CollectionView.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.dosesType?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "doseColletion", for: indexPath as IndexPath) as? DoseCollection {
cell.setButtonConfig(doseType: self.dosesType![indexPath.row].metodo , index: indexPath.row)
return cell
}
return UICollectionViewCell()
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("tapped")
}
}
CollectionCell
import UIKit
class DoseCollection: UICollectionViewCell {
//Outlets
#IBOutlet weak var Button: Button!
//Variables
let constants = Constants()
func setButtonConfig(doseType: String, index: Int){
self.Button.titleLabel?.text = doseType
self.Button.backgroundColor = constants.COLOR_ARRAY[index]
}
override func layoutSubviews() {
super.layoutSubviews()
}
override func layoutIfNeeded() {
super.layoutIfNeeded()
}
}
Potential Solutions:
1) Should be Single Selection for tableView selection property, programmatically it can be done by tableView.allowsSelection = true
2) The class is not the UITableViewDelegate for that table view, though UITableViewController is supposed to set that automatically.
tableView?.delegate = self
3) If the problem arise with UITapGestureRecognizer:
let tap = UITapGestureRecognizer(target: self, action:Selector("dismissKeyboard"))
view.addGestureRecognizer(tap)
tap.cancelsTouchesInView = false
BOL :)
In my case, I want to change the background of the button in other words the background of the cell in the collection view:
class CustomCVCell: UICollectionViewCell {
override var isSelected: Bool {
didSet {
grayBackgroundViewWithImage.image =
isSelected ? UIImage(named: "") : UIImage()
}
}
In the main class where the collection view is stored create this variable:
class CustomViewController: UIViewController {
///save the indexPath of last selected cell
private var lastSelectedIndexPath: IndexPath? }
In viewDidLoad() set this value to false:
customCollectionView.allowsMultipleSelection = false
Further code in data source. In my case, the first cell should be is selected:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CustomCVCell.cellID(),
for: indexPath) as! CustomCVCell
if indexPath.row == 0 {
lastSelectedIndexPath = indexPath
cell.isSelected = true
}
//update last select state from lastSelectedIndexPath
cell.isSelected = (lastSelectedIndexPath == indexPath)
return cell
}
Further code in the delegate:
///UICollectionViewDelegate
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard lastSelectedIndexPath != indexPath else { return }
if let index = lastSelectedIndexPath {
let cell = collectionView.cellForItem(at: index) as! CustomCVCell
cell.isSelected = false
}
let cell = collectionView.cellForItem(at: indexPath) as! CustomCVCell
cell.isSelected = true
lastSelectedIndexPath = indexPath
}

Collection View - How to select only one cell in each section

My collection view have multiple section. What I'm trying to achieve is that user can only select one cell (answer) in each section. When the cell (answer) have been selected, the background color will change.
What i failed to do is that Example : when user click on a cell in section 1, I want to deselect only the other cell in section 1.
Below are some of my code
#IBOutlet var step3CollectionView: UICollectionView!
var HexColor = HexColorClass()
var dataPageThree : json_PageThree!
var step3AnswerArray : [Int] = []
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
var frameCount = dataPageThree.step_instruction.first!.subquestions.count
for i in 0..<frameCount{
if indexPath.section == i {
step3AnswerArray[i] = (dataPageThree.step_instruction.first?.subquestions[i].subquestion_selection_answerNums![indexPath.row])!
let callCell = self.step3CollectionView.cellForItem(at: indexPath) as? Step3CollectionViewCell
callCell!.answerLabel.backgroundColor = HexColor.hexStringToUIColor(hex: "117577")
callCell!.answerLabel.textColor = UIColor.white
}
}
}
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
let indexPaths = collectionView.indexPathsForSelectedItems
if (indexPaths?.count) ?? 0 > 0 {
/// If you need simple way
for index in indexPaths! {
if index.section == indexPath.section {
self.step3CollectionView.deselectItem(at: index, animated: true) // if want deselect previous selection
let callCell = self.step3CollectionView.cellForItem(at: index) as? Step3CollectionViewCell
callCell!.answerLabel.backgroundColor = UIColor.white
callCell!.answerLabel.textColor = UIColor.black
//return false //if you do not want further selection
}
}
}
return true
}
Need some guidance.
First of all, set the collectionView's allowsMultipleSelection property to true, i.e.
override func viewDidLoad() {
super.viewDidLoad()
self.step3CollectionView.allowsMultipleSelection = true
}
Now, the UICollectionViewDelegate method collectionView(_: shouldSelectItemAt:) should look like,
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
collectionView.indexPathsForSelectedItems?.filter({ $0.section == indexPath.section }).forEach({ collectionView.deselectItem(at: $0, animated: false) })
return true
}
Also, don't change the backgroundColour of the cell in shouldSelectItemAt or didSelectItemAt based on cell's selection.
That makes the code bulky and redundant.
It should be done within the UICollectionViewCell subclass by overriding isSelected property.
class Step3CollectionViewCell: UICollectionViewCell {
override var isSelected: Bool {
didSet {
self.answerLabel.backgroundColor = isSelected ? HexColor.hexStringToUIColor(hex: "117577") : .white
self.answerLabel.textColor = isSelected ? .white : .black
}
}
}
With the above code, there is no need to write the color change code in collectionView(_:didSelectItemAt:) method as well. The UI for selection and de-selection of the cell will be automatically handled.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
step3AnswerArray[indexPath.section] = (dataPageThree.step_instruction.first?.subquestions[i].subquestion_selection_answerNums![indexPath.row])!
}

UICollectionView inside UITableViewCell returning empty always even though shows as selected

I am using a UICollectionView inside UITableViewCell. I am able to select the cells inside the UICollectionView. But when i try to get the UICollectionView or selected cells, the result is always null.I have been stuck on this for a long time. i included my code below for your reference.
class WeekDaysSelCell: UITableViewCell,UICollectionViewDelegate, UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {
var weekdays = ["S", "M", "T", "W", "T", "F", "S"]
var weekdaysSelected = [String]()
#IBOutlet var weeklyDaysColView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
self.weeklyDaysColView.delegate = self
self.weeklyDaysColView.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 7
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell : WeekDaysCollCell = weeklyDaysColView.dequeueReusableCell(withReuseIdentifier: "weekday", for: indexPath) as! WeekDaysCollCell
cell.weekDayLabel.text = weekdays[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell : WeekDaysCollCell = self.weeklyDaysColView.cellForItem(at: indexPath) as! WeekDaysCollCell
if (cell.backgroundColor == UIColor.gray) {
cell.backgroundColor = UIColor.clear
weekdaysSelected.removeAll { $0 == String(indexPath.row)}
//print("Removed from weekdaysSelected:", indexPath.row)
} else {
cell.backgroundColor = UIColor.gray
cell.isSelected = true
//weeklyDaysColView.selectItem(at: indexPath, animated: true, scrollPosition: [])
weekdaysSelected.append(String(indexPath.row))
//print("Added to weekdaysSelected:", indexPath.row)
}
}
}
// Trying to get the collection view from inside a willMove(toParent parent: UIViewController?) method.
override func willMove(toParent parent: UIViewController?) {
super.willMove(toParent: parent)
if parent == nil
{
if let delegate = self.delegate {
print("Inside If condition")
// Code that i use to get the cell
let cell3 = tableView.dequeueReusableCell(withIdentifier: "cell3") as! WeekDaysSelCell
print(cell3.weekdaysSelected)
print(cell3.weeklyDaysColView.indexPathsForSelectedItems)
// Trying to pass selected cells
//delegate.repeatCustomSelection(selectedIdx: String(lastSelection.row),repeatCustomSel: repeatCustomSelection)
}
}
}
You are trying to get a reusable cell in willMove(toParent parent: UIViewController?) , this is not going to return you a expected cell.
You need to get the cell , using a indexPath .
func cellForRow(at indexPath: IndexPath) -> UITableViewCell?
#andyPaul, is right you are generating the new cell in willMove(toParent parent: UIViewController?). Instead of that you have to pass the indexpath pf collection view when ever user selected any cell to your controller from the tableView Cell Class.
Now What is TypeAlias you can read from this link about type alias:- https://www.programiz.com/swift-programming/typealias
Create the typeAlias on above of your tableViewCell Class like this:-
typealias closureBlock = (_ isCapture : AnyObject?) ->()
class tableViewCellClass: UITableViewCell {
var callBack: closureBlock?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
Just Go to CollectionView didSelectItemAt Method and use this code after your coding
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell : WeekDaysCollCell = self.weeklyDaysColView.cellForItem(at: indexPath) as! WeekDaysCollCell
if (cell.backgroundColor == UIColor.gray) {
cell.backgroundColor = UIColor.clear
weekdaysSelected.removeAll { $0 == String(indexPath.row)}
//print("Removed from weekdaysSelected:", indexPath.row)
} else {
cell.backgroundColor = UIColor.gray
cell.isSelected = true
//weeklyDaysColView.selectItem(at: indexPath, animated: true, scrollPosition: [])
weekdaysSelected.append(String(indexPath.row))
//print("Added to weekdaysSelected:", indexPath.row)
}
guard let callBackClosure = self.callBack else {
return
}
callBackClosure(indexPath as AnyObject)
// You can pass any value here either indexpath or Array.
}
}
Now you have to initialise this closure so that it can check whether in which controller it will return the value when you assign the value from CollectionView didSelectItemAt Method.
Go to your ViewController Class where you have added the tableview and their datasources.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//You have to pass your tableview Cell Instance here and their reuse Identifier
let cell = tableView.dequeueReusableCell(withIdentifier: "tableViewCellClass", for: indexPath) as! tableViewCellClass
cell.callBack = { [weak self] (selectedIndexPath) -> ()in
// You will get the current selected index path of collection view here, Whenever you pass any index path from collectionView did SelectItem Method.
print(selectedIndexPath)
}
return cell
}

Nested Controllers using UICollectionViewController reordering

I have a UIViewController with a UICollectionViewController nested inside. The collection view controller also has the moveItemAt method implemented, since I want the cells to be reorderable. So the cells have a UILongPressGestureRecognizer attached. However, long press on the cells aren't happening. I can't seem to figure out if the nested controller is causing the gesture to be ignored. Maybe the parent controller is capturing the long press but that wouldn't make sense since AFAIK, gestures go up the view hierarchy, not the other way around.
For some context, I've used this method to nest my controllers
func add(_ child: UIViewController) {
addChildViewController(child)
child.view.frame = view.bounds
view.addSubview(child.view)
child.didMove(toParentViewController: self)
}
I think this is a better approach...
Assuming you have a UICollectionViewController in your storyboard, and you've assigned the cell prototype to DragMeCell and set its Identifier to "DragMeCell" (and added a label connected to the IBOutlet), this should run and allow you to long-press drag-drop to reorder.
//
// DragReorderCollectionViewController.swift
// SW4Temp
//
// Created by Don Mag on 7/25/18.
//
import UIKit
private let reuseIdentifier = "DragMeCell"
class DragMeCell: UICollectionViewCell {
#IBOutlet var theLabel: UILabel!
}
class DragReorderCollectionViewController: UICollectionViewController {
fileprivate var longPressGesture: UILongPressGestureRecognizer!
fileprivate var dataArray = Array(0 ..< 25)
override func viewDidLoad() {
super.viewDidLoad()
longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(self.handleLongGesture(gesture:)))
if let cv = self.collectionView {
cv.addGestureRecognizer(longPressGesture)
}
}
#objc func handleLongGesture(gesture: UILongPressGestureRecognizer) {
if let cv = self.collectionView,
let gestureView = gesture.view {
switch(gesture.state) {
case .began:
guard let selectedIndexPath = cv.indexPathForItem(at: gesture.location(in: cv)) else {
break
}
cv.beginInteractiveMovementForItem(at: selectedIndexPath)
case .changed:
cv.updateInteractiveMovementTargetPosition(gesture.location(in: gestureView))
case .ended:
cv.endInteractiveMovement()
default:
cv.cancelInteractiveMovement()
}
}
}
// MARK: UICollectionViewDelegate
override func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
return true
}
override func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let i = dataArray[sourceIndexPath.item]
dataArray.remove(at: sourceIndexPath.item)
dataArray.insert(i, at: destinationIndexPath.item)
}
// MARK: UICollectionViewDataSource
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataArray.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! DragMeCell
// Configure the cell
cell.theLabel.text = "\(dataArray[indexPath.item])"
return cell
}
}
Then you can add it as a child view controller, using the code snippet you posted in your question.

UICollectionView - Select all cells doesn't update properly

I have a button which selects all cells in the collectionview. Once clicked, the button function changes so that all cells will be de-selected upon pressing it again.
So far so good.. But
1) When you select all cells with the button, scroll a bit down and to the top again
2) Then de-select all cells with the button, and select all cells with the button again
3) And start scrolling down, some cells (mostly 1-2 complete rows, later cells are fine again) are not properly updated, so they don't appear with the selected state which is a different background color. Seems like an issue with dequeueReusableCell, but I can't wrap my head around it..
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if cell.isSelected {
cell.backgroundColor = UIColor.green
} else {
cell.backgroundColor = UIColor.white
}
if cell.viewWithTag(1) != nil {
let cellTitle = cell.viewWithTag(1) as! UILabel
cellTitle.text = String(indexPath.row)
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) {
cell.backgroundColor = UIColor.green
selectedCells.append(indexPath.row)
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) {
cell.backgroundColor = UIColor.white
selectedCells.removeObject(indexPath.row)
}
}
And the action method for handling button clicking
#IBAction func selectButtonTapped(_ sender: Any) {
if isSelectAllActive {
// Deselect all cells
selectedCells.removeAll()
for indexPath: IndexPath in collectionView!.indexPathsForSelectedItems! {
collectionView!.deselectItem(at: indexPath, animated: false)
collectionView(collectionView!, didDeselectItemAt: indexPath)
let cell: UICollectionViewCell
cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CVCell", for: indexPath)
}
selectButton.title = "Select all"
isSelectAllActive = false
} else {
// Select all cells
for i in 0 ..< collectionView!.numberOfItems(inSection: 0) {
collectionView!.selectItem(at: IndexPath(item: i, section: 0), animated: false, scrollPosition: UICollectionViewScrollPosition())
collectionView(collectionView!, didSelectItemAt: IndexPath(item: i, section: 0))
}
selectedCells.removeAll()
let indexPaths: [IndexPath] = collectionView.indexPathsForSelectedItems!
for item in indexPaths {
selectedCells.append(item.row)
}
selectedCells.sort{$0 < $1}
selectButton.title = "Select none"
isSelectAllActive = true
}
}
And for completion the array extension for removing an object
extension Array where Element : Equatable {
mutating func removeObject(_ object : Iterator.Element) {
if let index = self.index(of: object) {
self.remove(at: index)
}
}
}
Complete Xcode project can be found here: https://www.dropbox.com/s/uaj1asg43z7bl2a/SelectAllCells.zip
Used Xcode 9.0 beta 1, with iOS11 Simulator/iPhone SE
Thanks for your help!
Your code is a little confused because you are trying to keep track of cell selection state both in an array and in the cell itself.
I would just use a Set<IndexPath> as it is simpler and more efficient than an array. You can then refer to this set when returning a cell in cellForItemAt: and you don't need to do anything in willDisplay.
When you select/deselect all you can just reload the whole collection view and when an individual cell is selected/deselected, just reload that cell.
#objcMembers
class MainViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet var collectionView: UICollectionView!
#IBOutlet var toolBar: UIToolbar?
#IBOutlet weak var selectButton: UIBarButtonItem!
var selectedCells = Set<IndexPath>()
var isSelectAllActive = false
// MARK: - Classes
override func viewDidLoad() {
super.viewDidLoad()
// Collection view
collectionView!.delegate = self
collectionView!.dataSource = self
collectionView!.allowsMultipleSelection = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
}
#IBAction func selectButtonTapped(_ sender: Any) {
if isSelectAllActive {
// Deselect all cells
selectedCells.removeAll()
selectButton.title = "Select all"
isSelectAllActive = false
} else {
// Select all cells
for i in 0 ..< collectionView!.numberOfItems(inSection: 0) {
self.selectedCells.insert(IndexPath(item:i, section:0))
}
selectButton.title = "Select none"
isSelectAllActive = true
}
self.collectionView.reloadData()
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 50
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: UICollectionViewCell
cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CVCell", for: indexPath)
if self.selectedCells.contains(indexPath) {
cell.backgroundColor = .green
} else {
cell.backgroundColor = .white
}
if cell.viewWithTag(1) != nil {
let cellTitle = cell.viewWithTag(1) as! UILabel
cellTitle.text = String(indexPath.row)
}
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("\ndidSelectItemAt: \(indexPath.row)")
if selectedCells.contains(indexPath) {
selectedCells.remove(indexPath)
} else {
selectedCells.insert(indexPath)
}
self.collectionView.deselectItem(at: indexPath, animated: false)
self.collectionView.reloadItems(at: [indexPath])
print("selectedCells: \(selectedCells)")
}
}

Resources