Swift UITableView visually deselects cells on scroll - uitableview

I made an app that lists items in a UITableView. When I select items and scroll down till they go off screen, they will become visually deselected, meaning:
The checkbox image we set and the backgroundcolor are reset to original state.
The system itself however, does actually know what was selected and what wasnt.
Code:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:TblCell! = self.tableView.dequeueReusableCellWithIdentifier("cell") as TblCell!
cell.lblCarName.text = tableData[indexPath.row]
cell.lblPrice.text = tablePrice[indexPath.row]
if (tableAvailability[indexPath.row] == "NO") {
cell.imgCarName.image = UIImage(named: "nonselectable")
cell.lblPrice.textColor = UIColor(red: 172/255, green: 76/255, blue: 67/255, alpha: 1);
} else {
cell.imgCarName.image = UIImage(named: "deselected")
}
cell.selectionStyle = UITableViewCellSelectionStyle.None;
return cell
}
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
let cell:TblCell = tableView.cellForRowAtIndexPath(indexPath) as TblCell
if (tableAvailability[indexPath.row] == "YES") {
println("Row \(indexPath.row) selected")
//var myBackView = UIView(frame: cell.frame)
cell.backgroundColor = UIColor(red: 190/255, green: 225/255, blue: 255/255, alpha: 1);
//cell.selectedBackgroundView = myBackView
cell.imgCarName.image = UIImage(named: "selected")
}
}
func tableView(tableView: UITableView!, didDeselectRowAtIndexPath indexPath: NSIndexPath!) {
let cell:TblCell = tableView.cellForRowAtIndexPath(indexPath) as TblCell
if (tableAvailability[indexPath.row] == "YES") {
println("Row \(indexPath.row) deselected")
//var myBackView = UIView(frame: cell.frame)
cell.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 1);
//cell.selectedBackgroundView = myBackView
cell.imgCarName.image = UIImage(named: "deselected")
}
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 70
}
Any idea on how to fix this?
thanks in advance.

On 'didSelectRowAtIndexPath', you change the backgroundColor and imgCarName straight on the cell.
When you scroll, your cell gets reused! Meaning that the same cell is destroyed and used to present new content.
To keep track of what is selected, you need to save that state somewhere else than against the cell, maybe in your tableAvailability object, or any other object that handles the content of your cells.

Related

How to avoid Reusable Cell's copy behavior

Here is the problem, when I use a TableViewController and add a behavior on cell been selected. The behavior showed twice
How can I avoid this?
// MARK: - Table Deleget
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
UIView.animate(withDuration: 0.2, animations: {
cell?.viewWithTag(100)?.isHidden = true
(cell?.viewWithTag(200) as! UILabel).textColor = UIColor.red
})
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
UIView.animate( withDuration: 0.3, animations: {
cell?.viewWithTag(100)?.isHidden = false
(cell?.viewWithTag(200) as! UILabel).textColor = UIColor(red: 0, green: 128/255, blue: 128/255, alpha: 1)
})
}
Move the animation from the 'cellForRow' method to 'willDisplayCell' method. I think it can help to you avoid the twice animation.
I have fixed it, add a var to remember the cell which has been taped, and use cellWillDisplay to refresh every cell will displayed, check each cell if it has been selected, if has, show it the selected way.
// MARK: - Table Deleget
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
index = indexPath.row
if let cell = tableView.cellForRow(at: indexPath){
UIView.animate(withDuration: 0.2, animations: {
cell.viewWithTag(100)?.isHidden = true
(cell.viewWithTag(200) as! UILabel).textColor = UIColor.red
})
}
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
UIView.animate( withDuration: 0.3, animations: {
cell.viewWithTag(100)?.isHidden = false
(cell.viewWithTag(200) as! UILabel).textColor = UIColor(red: 0, green: 128/255, blue: 128/255, alpha: 1)
})
}
}
// Added this
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if let index = index, index == indexPath.row {
cell.viewWithTag(100)?.isHidden = true
(cell.viewWithTag(200) as! UILabel).textColor = UIColor.red
} else {
cell.viewWithTag(100)?.isHidden = false
(cell.viewWithTag(200) as! UILabel).textColor = UIColor(red: 0, green: 128/255, blue: 128/255, alpha: 1)
}
}

UICollectionview Reuse Existing cell

I want to generate collectionview with 100 number of cells, But it should not to be reallocate at every time of scrolling, But in my code its always creating cells newly.
any one help me to avoid this issue, please find my code below,
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return colorArray.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let colleCell: colorCell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! colorCell
colleCell.bgColor.backgroundColor = UIColor(red: colorArray[indexPath.row].valueForKey("Red") as! CGFloat/255, green: colorArray[indexPath.row].valueForKey("Green") as! CGFloat/255, blue: colorArray[indexPath.row].valueForKey("Blue") as! CGFloat/255, alpha: 1.0)
return colleCell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath)
{
let cell:colorCell = collectionView.cellForItemAtIndexPath(indexPath) as! colorCell
cell.layer.borderWidth = 2.0
cell.layer.borderColor = UIColor.whiteColor().CGColor
selectedColor = indexPath.row
sampleColorView.backgroundColor = UIColor(red: colorArray[indexPath.row].valueForKey("Red") as! CGFloat/255, green: colorArray[indexPath.row].valueForKey("Green") as! CGFloat/255, blue: colorArray[indexPath.row].valueForKey("Blue") as! CGFloat/255, alpha: 1.0)
self.view.backgroundColor = UIColor(red: colorArray[indexPath.row].valueForKey("Red") as! CGFloat/255, green: colorArray[indexPath.row].valueForKey("Green") as! CGFloat/255, blue: colorArray[indexPath.row].valueForKey("Blue") as! CGFloat/255, alpha: 0.7)
sampleColorView.layer.cornerRadius = 75
}
func collectionView(collectionView: UICollectionView, didDeselectItemAtIndexPath indexPath: NSIndexPath)
{
let cell:colorCell = collectionView.cellForItemAtIndexPath(indexPath) as! colorCell
selectedIndex = -1;
cell.layer.borderWidth = 0.0
cell.layer.borderColor = UIColor.grayColor().CGColor
self.view.backgroundColor = UIColor.grayColor()
}
I think it's reused correctly, plz double check? But I suggest the way to set selected/deselected cell's style, you can override selected property of UICollectionViewCell.
class ColorCell : UICollectionViewCell{
override var selected: Bool{
didSet {
layer.borderWidth = selected ? 2 : 0
layer.borderColor = selected ? UIColor.whiteColor().CGColor : UIColor.grayColor().CGColor
}
/* your code */
}
and UICollectionViewDelegate implementation:
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
selectedColor = indexPath.row
sampleColorView.backgroundColor = /*Color*/
view.backgroundColor = /*Color*/
sampleColorView.layer.cornerRadius = 75
}
func collectionView(collectionView: UICollectionView, didDeselectItemAtIndexPath indexPath: NSIndexPath) {
selectedIndex = -1;
self.view.backgroundColor = UIColor.grayColor()
}

Highlight Cell for Multiple selection

I need to change the colour of my cell upon selection to a desired UI Color. Below is my code, and it is working fine. However, why the checkbox area is still in grey colour? How do I change that?
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selectedCell:UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
selectedCell.contentView.backgroundColor = UIColor(red: 43.0/255.0, green: 240.0/255.0, blue: 12.0/255.0, alpha: 1.0)
}
I don't have enough reputations to post images. Sorry in advance!
Image
Maybe this is helpfull from my code snippet
var colorVw = UIView()
colorVw.backgroundColor = UIColor.redColor()
cell.selectedBackgroundView = colorVw
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath)
cell.accessoryView = nil
}

UITableViewCell Selected Background Color on Multiple Selection

// Doesn't work
cell.selectionStyle = .Blue
//Works when the selection is not multiple, if it's multiple with each selection the previous one disappear...
let cellBGView = UIView()
cellBGView.backgroundColor = UIColor(red: 0, green: 0, blue: 200, alpha: 0.4)
cell.selectedBackgroundView = cellBGView
Any answer how to set background color of the cells which are selected?
All the above answers are fine but a bit to complex to my liking. The simplest way to do it is to put some code in the cellForRowAtIndexPath. That way you never have to worry about changing the color when the cell is deselected.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
/* this is where the magic happens, create a UIView and set its
backgroundColor to what ever color you like then set the cell's
selectedBackgroundView to your created View */
let backgroundView = UIView()
backgroundView.backgroundColor = YOUR_COLOR_HERE
cell.selectedBackgroundView = backgroundView
return cell
}
This worked for me:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var selectedCell:UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
selectedCell.contentView.backgroundColor = UIColor.redColor()
}
// if tableView is set in attribute inspector with selection to multiple Selection it should work.
// Just set it back in deselect
override func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
var cellToDeSelect:UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
cellToDeSelect.contentView.backgroundColor = colorForCellUnselected
}
//colorForCellUnselected is just a var in my class
Swift 4.2
For multiple selections you need to set the UITableView property allowsMultipleSelection to true.
myTableView.allowsMultipleSelection = true
In case you subclassed the UITableViewCell, you override setSelected(_ selected: Bool, animated: Bool) method in your custom cell class.
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected {
contentView.backgroundColor = UIColor.green
} else {
contentView.backgroundColor = UIColor.blue
}
}
Swift 3
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "yourCellIdentifier", for: indexPath)
cell.selectionStyle = .none
return cell
}
Swift 2
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "yourCellIdentifier", for: indexPath)
cell.selectionStyle = .None
return cell
}
The problem with Kersnowski's approach is that when the cell is redrawn the changes made when it's selected/deselected will be gone. So I would move the changes into the cell itself, which means subclassing is required here. For example:
class ICComplaintCategoryCell: UITableViewCell {
#IBOutlet var label_title: UILabel!
#IBOutlet var label_checkmark: UILabel!
override func layoutSubviews() {
super.layoutSubviews()
reload()
}
func reload() {
if isSelected {
contentView.backgroundColor = UIColor.red
}
else if isHighlighted{
contentView.backgroundColor = UIColor.red
}
else {
contentView.backgroundColor = UIColor.white
}
}
}
And in your table view delegate just call reload:
if let cell = self.table.cellForRowAtIndexPath(path) as? ICComplaintCategoryCell {
cell.reload()
}
Updated for Swift 3+, thanks #Bogy
You can also set cell's selectionStyle to.none in interface builder. The same solution as #AhmedLotfy provided, only from IB.
For Swift 3,4 and 5 you can do this in two ways.
1) class: UITableViewCell
override func awakeFromNib() {
super.awakeFromNib()
//Costumize cell
selectionStyle = .none
}
or
2) tableView cellForRowAt
cell.selectionStyle = .none
If you want to set selection color for specific cell, check this answer:
https://stackoverflow.com/a/56166325/7987502
UITableViewCell has an attribute multipleSelectionBackgroundView.
https://developer.apple.com/documentation/uikit/uitableviewcell/1623226-selectedbackgroundview
Just create an UIView define the .backgroundColor of your choice and assign it to your cells .multipleSelectionBackgroundView attribute.
Swift 3
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedCell:UITableViewCell = tableView.cellForRow(at: indexPath)!
selectedCell.contentView.backgroundColor = UIColor.darkGray
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let selectedCell:UITableViewCell = tableView.cellForRow(at: indexPath)!
selectedCell.contentView.backgroundColor = UIColor.clear
}
By adding a custom view with the background color of your own you can have a custom selection style in table view.
let customBGColorView = UIView()
customBGColorView.backgroundColor = UIColor(hexString: "#FFF900")
cellObj.selectedBackgroundView = customBGColorView
Add this 3 line code in cellForRowAt method of TableView.
I have used an extension in UIColor to add color with hexcode. Put this extension code at the end of any Class(Outside the class's body).
extension UIColor {
convenience init(hexString: String) {
let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
var int = UInt32()
Scanner(string: hex).scanHexInt32(&int)
let a, r, g, b: UInt32
switch hex.characters.count {
case 3: // RGB (12-bit)
(a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
case 6: // RGB (24-bit)
(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
case 8: // ARGB (32-bit)
(a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
default:
(a, r, g, b) = (255, 0, 0, 0)
}
self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255)
}
}
SWIFT 3/4
Solution for CustomCell.selectionStyle = .none if you set some else style you saw "mixed" background color with gray or blue.
And don't forget! func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) didn't call when CustomCell.selectionStyle = .none.
extension MenuView: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cellType = menuItems[indexPath.row]
let selectedCell = tableView.cellForRow(at: indexPath)!
selectedCell.contentView.backgroundColor = cellType == .none ? .clear : AppDelegate.statusbar?.backgroundColor?.withAlphaComponent(0.15)
menuItemDidTap?(menuItems[indexPath.row])
UIView.animate(withDuration: 0.15) {
selectedCell.contentView.backgroundColor = .clear
}
}
}
Swift 5 - This works for me:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedCell:UITableViewCell = tableView.cellForRow(at: indexPath as IndexPath)!
selectedCell.contentView.backgroundColor = .red
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let cellToDeSelect:UITableViewCell = tableView.cellForRow(at: indexPath as IndexPath)!
cellToDeSelect.contentView.backgroundColor = .clear
}
You can use standard UITableViewDelegate methods
- (nullable NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
EntityTableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
[cell selectMe];
return indexPath;
}
- (nullable NSIndexPath *)tableView:(UITableView *)tableView willDeselectRowAtIndexPath:(NSIndexPath *)indexPath {
EntityTableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
[cell deSelectMe];
return indexPath;
}
in my situation this works, cause we need to select cell, change color, and when user taps 2 times on the selected cell further navigation should be performed.
Swift 4
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let selectedCell = tableView.cellForRow(at: indexPath)! as! LeftMenuCell
selectedCell.contentView.backgroundColor = UIColor.blue
}
If you want to unselect the previous cell, also you can use the different logic for this
var tempcheck = 9999
var lastrow = IndexPath()
var lastcolor = UIColor()
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
if tempcheck == 9999
{
tempcheck = 0
let selectedCell = tableView.cellForRow(at: indexPath)! as! HealthTipsCell
lastcolor = selectedCell.contentView.backgroundColor!
selectedCell.contentView.backgroundColor = UIColor.blue
lastrow = indexPath
}
else
{
let selectedCelllasttime = tableView.cellForRow(at: lastrow)! as! HealthTipsCell
selectedCelllasttime.contentView.backgroundColor = lastcolor
let selectedCell = tableView.cellForRow(at: indexPath)! as! HealthTipsCell
lastcolor = selectedCell.contentView.backgroundColor!
selectedCell.contentView.backgroundColor = UIColor.blue
lastrow = indexPath
}
}

UICollectionView and selected UICollectionViewCell

I'm trying to do a simple UICollectionView with a custom cell composed of an UIImageView and an UIView above.
When a cell is not selected, the UIView on the top of the cell have is backgroundColor property set to UIColor(red: 1, green: 1, blue: 1, alpha: 0.5).
I have issues with the selection. The collectionView:didSelectItemAtIndexPath: method is called but when I change the UIView described previously, nothing happen.
Here is the code of my collectionView :
class TestCollectionViewController: UICollectionViewController
{
var items = [1, 2, 3]
let cellId = "Cell"
override func viewDidLoad()
{
self.collectionView.allowsMultipleSelection = true
self.collectionView.delaysContentTouches = false
}
override func collectionView(collectionView: UICollectionView!, numberOfItemsInSection section: Int) -> Int
{
return items.count
}
override func collectionView(collectionView: UICollectionView!, cellForItemAtIndexPath indexPath: NSIndexPath!) -> UICollectionViewCell!
{
var cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellId,
forIndexPath: indexPath) as TestingCollectionViewCell
let item = items[indexPath.row]
cell.imageView.image = UIImage(named: "img")
cell.overlayView.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.5)
return cell
}
override func collectionView(collectionView: UICollectionView!, didSelectItemAtIndexPath indexPath: NSIndexPath!)
{
var cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellId,
forIndexPath: indexPath) as TestingCollectionViewCell
cell.overlayView.backgroundColor = UIColor.clearColor()
}
override func collectionView(collectionView: UICollectionView!, didDeselectItemAtIndexPath indexPath: NSIndexPath!)
{
var cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellId,
forIndexPath: indexPath) as TestingCollectionViewCell
cell.overlayView.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.5)
}
}
If this snippet is not enough, here is my project
You should not be dequeuing a new cell in didSelectItemAtIndexPath (or in didDeselect either) -- this is creating (or reusing) a new cell, not getting the one you selected. Use this instead,
var cell = collectionView.cellForItemAtIndexPath(indexPath)
To me the following code worked better (the code of rdelmar crashed sometimes, which might be a bug? in Swift):
(collectionView.cellForItemAtIndexPath(indexPath) as myCollectionViewCellType).uiBackground?.backgroundColor = UIColor.blackColor()
Edit: I forgot to mention: My collectionView-Items are based on their own class to which I cast. uiBackground is an element of that class
Swift 3.0
let cell = collectionView.cellForItem(at: indexPath)
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath)
{
//saving index path of the image loaded from imageproperty_array
selected_imagepath = imageproperty_array[indexPath.row] as String
//saving index of the selected cell item as Int
selected_imageindex = indexPath.row
}

Resources