How to allow multiple UICollectionViewCells to be selected and deselected? - ios

I have a UICollectionView (with .allowsMultipleSelection = true), and I want it so that whenever I tap on a cell, it changes color, and when I tap it again, it reverts to its old color. I understand that UICollectionViewCells have a isSelected property, so I've tried manipulating that to change the colors.
override var isSelected: Bool {
didSet {
if isSelected == false {
contentView.backgroundColor = .lightGray
roleLabel.textColor = .gray
} else {
contentView.backgroundColor = .gray
roleLabel.textColor = .black
print("selected")
}
}
}
My the view controller class that contains the collection view in question has defined didSelectItemAt. However, whenever I tap on the cell, "selected" is printed each time - meaning regardless of how many times I've tapped on the cell, it still sets isSelected to be true each time - and the color doesn't change. Why is this happening?

you have to Store your selected cell in array.for example just store indexpath of selected Cell in Array than Check if indexpath is Already present in Array that means it is Already Selected.
var slectedCell = [IndexPath]()
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if slectedCell.contains(indexPath) {
cell.backgroundColor = UIColor.gray
slectedCell = slectedCell.filter { $0 != indexPath }
}
else {
slectedCell.append(indexPath)
cell.backgroundColor = UIColor.lightGray
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if slectedCell.contains(indexPath) {
cell.backgroundColor = UIColor.gray
}
else {
cell.backgroundColor = UIColor.lightGray
}
}

Related

change the cell view color when it select. And change it back when selected another cell

I have an collection view cell.I each cell i have an view. As id now default, i set the border color of my view to lightGray.
So when ever i select any cell, i needs to change my view border color to red color. And again if i select any other new cell. My old cell view should change back to lightGray.And new cell view have to display as redcolor.
how can i do that :
in my cell :
#IBOutlet var baseView: UIView! {
didSet {
baseView.layer.cornerRadius = 5
baseView.layer.borderWidth = 1.0
baseView.layer.borderColor = UIColor.lightGray.cgColor
}
}
let datt = [["duration": "No", "price": "Rs. 100", "perMonth": "per month"],
["duration": "12", "price": "Rs. 55.20", "perMonth": "per month"],
["duration": "No", "price": "Rs. 1300", "perMonth": "one time"]]
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "KMSubscriptionsCell", for: indexPath) as! KMSubscriptionsCell
let subcription = subscriptions[indexPath.item]
cell.durationLabel.text = datt["duration"]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! KMSubscriptionsCell
cell.baseView.layer.borderColor = Utils.colorCode.selectedBorderColor.cgColor
cell.baseView.layer.borderWidth = 2.0
}
I tried some :
in my cell for row :
if indexPath.item != indexPath.item {
cell.baseView.layer.borderWidth = 1.0
cell.baseView.layer.borderColor = UIColor.lightGray.cgColor
}
its doesnt work.even i added in did select.No luck.Please help me out. How can i achive that.
One easy way is to use a property observer in the cell class:
class CollectionViewCell: UICollectionViewCell {
override func awakeFromNib() {
super.awakeFromNib()
layer.borderWidth = 1
layer.borderColor = borderColor
}
override var isSelected: Bool {
didSet {
layer.borderColor = borderColor
}
}
private var borderColor: CGColor {
return isSelected ? UIColor.red.cgColor : UIColor.lightGray.cgColor
}
}
Instead of the cell itself you can also apply the border to your baseView instead.

How to change Color of cell in UiCollectionView if just two can be selected

I am used button inside UICollectionView cell, and I want to button can change color just two indexes and other can't change color if click other buttons uicolor.clear
and I want to like this, so how to use sender.backgroundColor
func collectionView(_: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellB", for: indexPath) as! BandingCollectionViewCell
cell.bBandingCell.addTarget(self, action: #selector(masterAction3(_:)), for: .touchUpInside)
return cell
}
{
#objc func masterAction3(_ sender: UIButton) {
var indexPath = collectionView.indexPath(for: ((sender.superview?.superview) as! BandingCollectionViewCell))
if sender.isSelected {
sender.isSelected = false
switch indexPath?.row {
case 0:
print("0")
sender.backgroundColor = UIColor.clear
case 1:
print("1")
sender.backgroundColor = UIColor.clear
default:
print("default")
sender.backgroundColor = UIColor.blue
}
} else {
sender.isSelected = true
switch indexPath?.row {
case 0:
print("0")
sender.backgroundColor = UIColor.blue
case 1:
print("1")
sender.backgroundColor = UIColor.blue
default:
print("default")
sender.backgroundColor = UIColor.clear
}
}
}
I'd do it this way:
Use custom UICollectionViewCell subclass (without button because collection view cell handles selection itself)
in this cell class override isSelected property like this:
override var isSelected: Bool {
didSet {
// set color according to state
self.backgroundColor = self.isSelected ? .blue : .clear
}
}
In class which controls your collectionView perform collectionView.allowsMultipleSelection = true
In your UICollectionViewDelegate implement method (which will prevent selection of more than two cells at a time):
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
return (collectionView.indexPathsForSelectedItems?.count ?? 0) < 2
}
This way you don't need a button inside the cell.
When you select a cell, isSelected will be set. And you can customise your cell like this.
class YourCollectionViewCell: UICollectionViewCell {
override var isSelected: Bool {
didSet {
self.contentView.backgroundColor = isSelected ?.blue : .clear
}
}
}
NB: No need to add actions manually. Remove your selector method

uibutton in collectionview cell action duplicating

So basically my problem is that when I click on a button which is present in collection view cell it should change the colour of button background colour. but the problem is it is changing the colour of another button. eg if I click on button 1 it changes the colour of button 6 automatically.
class hello: UICollectionViewCell {
#IBOutlet weak var btn: UIButton!
#IBAction func click(_ sender: Any) {
if btn.isSelected == true
{
btn.backgroundColor = UIColor.red
btn.isSelected = false
}
else{ btn.backgroundColor = UIColor.purple
btn.isSelected = true
}
}
override func prepareForReuse() {
super.prepareForReuse()
}
}
view controller file
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "happy", for: indexPath) as! hello
if cell.btn.isSelected
{
cell.btn.backgroundColor = UIColor.red
}
else{ cell.btn.backgroundColor = UIColor.purple
}
cell.btn.tag = indexPath.item
print(cell.btn.isSelected ,indexPath.row)
return cell
}
The problem is that the UICollectionView re-uses cell for optimized scroll performance. Hence it re-uses the cell at index 1 when displaying cell at index 6 for e.g. Therefore you need to set the state of the cell when ever it is updated/reused.
The following function is called everytime. So you need to set cell.btn. backgroundColor over here.
func collectionView(_ collectionView: UICollectionView, cellForItemAt
indexPath: IndexPath) -> UICollectionViewCell {
...
...
if dataSource[indexPath.row].selected {
btn.backgroundColor = UIColor.red
}else {
btn.backgroundColor = UIColor.purple
}
...
return cell
}
Now, it is upto your individual implementation, how you want to update the model when selection is changed. One option is you can define a protocol and implement it in your ViewController to update the values.

UICollectionView Reloading cells backwards

I am using a UICollectionView as a square grid layout for a crossword. The user will 'create' the puzzle by first tapping each cell to change the colour, press the 'insert numbers button' then add the numbers for each square and finally press the 'get clues button'. When the 'insert numbers button' is pressed, the cells are reloaded to 'fix' the colour and a textfield for each square's number is unhidden (for white cells only). The problem arises when I reload the cells to make the square's number un-editable, the cells reload in reverse. I.e. if the first cell (index 0) has a number, the last cell is updated (index 63). Pressing the button twice puts the cells in the correct position.
I have printed the indexPath to see which cell has the text and it is always the reverse...
Code:
import UIKit
class gridSetup: UIViewController, UICollectionViewDataSource,
UICollectionViewDelegate {
var setColours: Bool = true
var setNumbers: Bool = false
var numbers: Array = [Int]()
var whiteSquares:Array = [IndexPath]()
var gridSizer: Int? //value passed in from previous screen
#IBOutlet weak var getCluesButton: UIButton!
#IBOutlet weak var insertNumbersButton: UIButton!
#IBOutlet weak var grid: UICollectionView!
let reuseIdentifier = "cell"
override func viewDidLoad() {
super.viewDidLoad()
setColours = true
setNumbers = false
getCluesButton.isHidden = true
self.navigationController?.isNavigationBarHidden = false
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - UICollectionViewDataSource protocol
// tell the collection view how many cells to make
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//return (self.gridSizer * self.gridSizer)
return (self.gridSizer! * self.gridSizer!)
}
// make a cell for each cell index path
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// get a reference to our storyboard cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! MyCollectionViewCell
cell.backgroundColor = UIColor.white
if setColours == true {
whiteSquares.append(indexPath) //initalise whiteSquares by adding all the squares
cell.cellGuess.isHidden = true //hide the letter guesses
cell.squareNumber.isHidden = true //hide the sqaure numbers
}
//redraw cells to fix the colours
// if we are on a white cell and are done setting colours
if setColours == false && whiteSquares.contains(indexPath) == true {
if setNumbers == true {
cell.squareNumber.isHidden = false
}
else {
if cell.squareNumber.text != "" {
print("I have a number inside, Index: ", indexPath.row)
}
cell.squareNumber.isUserInteractionEnabled = false
cell.squareNumber.placeholder = ""
cell.cellGuess.isHidden = false
return cell
}
}
// on a black square
else if setColours == false && whiteSquares.contains(indexPath) == false {
cell.squareNumber.isHidden = true
//print("on a black square", indexPath)
cell.backgroundColor = UIColor.black
cell.squareNumber.isHidden = true
cell.cellGuess.isHidden = true
}
//initial grid setup
else {
cell.backgroundColor = UIColor.white
cell.layer.borderColor = UIColor.black.cgColor
cell.squareNumber.isHidden = true
}
cell.layer.borderWidth = 1
cell.layer.cornerRadius = 8
return cell
}
// MARK: - UICollectionViewDelegate protocol
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// handle tap events
//print("You selected cell #\(indexPath.item)!")
}
// change background color back when user releases touch
func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
if setColours == true {
if cell?.backgroundColor == UIColor.black {
//print("add", indexPath.row)
cell?.backgroundColor = UIColor.white
var k: Int = 0
for i in whiteSquares {
if indexPath.row < i.row {
whiteSquares.insert(indexPath, at: k)
break
}
else {
k += 1
}
}
}
else {
//print("remove", indexPath.row)
cell?.backgroundColor = UIColor.black
var k: Int = 0
for i in whiteSquares {
if indexPath.row == i.row {
whiteSquares.remove(at: k)
}
else {
k += 1
}
}
}
}
}
#IBAction func getClues(_ sender: Any) {
setNumbers = false
grid.reloadData() //problem comes from/after calling this function
}
#IBAction func insertNumbers(_ sender: Any) {
setColours = false
insertNumbersButton.isHidden = true
setNumbers = true
getCluesButton.isHidden = false
grid.reloadData()
}
}
// some layout stuff, (not important)
extension gridSetup: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.frame.size.width / (CGFloat(gridSizer! + 1))
//let width = collectionView.frame.size.width / (CGFloat(gridSizer! + 1))
let height = width
return CGSize(width: width, height: height)
}
}
The '1' should be in the 1st row, second column

isHighlighted and isSelected didSet only called for UICollectionViewCell not UITableViewCell

I want to apply some style changes to a custom table view cell on highlight/select so am overriding isHighlighted and isSelected to achieve this. It works for my custom collection view cells but not when I tap on the custom table view cells.
I set up the exact same scenario for a table view and collection view and implemented the following on the custom cells:
override var isHighlighted: Bool {
didSet {
//called when I tap for CustomCollectionViewCell not for CustomTableViewCell
}
}
override var isSelected: Bool {
didSet {
//called when I tap for CustomCollectionViewCell not for CustomTableViewCell
}
}
What am I missing here? Why doesn't the table view cell did set get called when it's tapped? This happens for any table view I try with regardless of the content of the custom cell.
The other answer did not work for me. I reckon the reason is that UITableViewCell.isSelected setter is never called when the containing UITableView handles selection, that state is passed via func setSelected(_ selected: Bool, animated: Bool) instead. This means that overriding this function in your UITableViewCell subclass instead of the setter works:
override func setSelected(_ selected: Bool, animated: Bool) {
// implementation that was meant to be in `isSelected` `didSet`
}
By overriding these properties this way, you are no longer using their default implementations.
Try relaying information to super:
override var isHighlighted: Bool {
get {
return super.isHighlighted
}
set {
//do something
super.isHighlighted = newValue
}
override var isSelected: Bool {
get {
return super.isSelected
}
set {
//do something
super.isSelected = newValue
}
}
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
}

Resources