Question

I have a UICollectionView that I have created programmatically. I would like for the collection view to behave in the following way:

1. User touches cell
2. Cell background color changes
3. User releases touch
4. Cell background color changes

This should be a quick color change that happens just before the selector related to the tap action is executed in which the viewcontroller containing the collection view is popped off the stack.

I have been looking at this question: UICollectionView cell change background while tap

in which there is the following summary of methods to use for this purpose:

// Methods for notification of selection/deselection and highlight/unhighlight events.
// The sequence of calls leading to selection from a user touch is:
//
// (when the touch begins)
// 1. -collectionView:shouldHighlightItemAtIndexPath:
// 2. -collectionView:didHighlightItemAtIndexPath:
//
// (when the touch lifts)
// 3. -collectionView:shouldSelectItemAtIndexPath: or -    collectionView:shouldDeselectItemAtIndexPath:
// 4. -collectionView:didSelectItemAtIndexPath: or -collectionView:didDeselectItemAtIndexPath:
// 5. -collectionView:didUnhighlightItemAtIndexPath:

I am assuming I only need to implement one of the above methods from 'when touch begins' and 'when touch ends.' But no matter what I do, it appears that a background color changes and then remains changed. Here is an example of something I attempted which did not work:

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
 {
   //pop vc 
 }

- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
  UICollectionViewCell* cell = [collectionView cellForItemAtIndexPath:indexPath];
  cell.contentView.backgroundColor = [UIColor redColor];
}

- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
{
  UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
  cell.contentView.backgroundColor = [UIColor greenColor];
}

This results in the cell background color being changed only to red. I also looked at this question: UICollectionView Select and Deselect issue and tried implementing [UICollectionView selectItemAtIndexPath:animated:scrollPosition:] and calling it inside of didSelectItemAtIndexPath, but this did not work either. Collection view data source and delegate are set.

Was it helpful?

Solution

The problem is that you are changing the color on highlight and changing it back on deselect instead that on unhighlight

You should simply change this:

- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
{
  UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
  cell.contentView.backgroundColor = [UIColor greenColor];
}

to this:

- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
  UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
  cell.contentView.backgroundColor = [UIColor greenColor];
}

Also, if you don't want to wait a bit before getting your highlight happen you should set the delaysContentTouches property of the collection view to NO

Edit: also ensure that you call

[collectionView deselectItemAtIndexPath:indexPath animated:NO];

inside the -didSelectItemAtIndexPath method

OTHER TIPS

Swift 3 version

Add the following two methods to your view controller class:

// change background color when user touches cell
func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath) {
    let cell = collectionView.cellForItem(at: indexPath)
    cell?.backgroundColor = UIColor.red
}

// change background color back when user releases touch
func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath) {
    let cell = collectionView.cellForItem(at: indexPath)
    cell?.backgroundColor = UIColor.green
}

See here for help in setting up a basic collection view in Swift.

Edit: Answer in Swift 3

var selectedIndex = Int ()

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell

    cell.backgroundColor = selectedIndex == indexPath.row ? UIColor.green : UIColor.red

    return cell
}

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
{
    selectedIndex = indexPath.row

    self.yourCollctionView.reloadData()
}

Here is my solution. And I'm sure it really works.
I provide three methods to highlight a cell (selectedBackgroundView, tint cell.contentView and tint a special area).

How to use:
1. just inherit BaseCollectionViewCell and do nothing;
2. inherit and set specialHighlightedArea = UIView(), and contentView.addSubView(specialHighlightedArea), then layout it or add constraint to use Auto Layout;
3. if you don't need highlight effect, just write a method named 'shouldHighlightItemAtIndexPath' defined by UICollectionViewDelegate and make it return false, or set cell.shouldTintBackgroundWhenSelected = false and set specialHighlightedArea = nil and remove it from superView.

/// same with UITableViewCell's selected backgroundColor
private let highlightedColor = UIColor(rgb: 0xD8D8D8) 

/// you can make all your collectionViewCell inherit BaseCollectionViewCell
class BaseCollectionViewCell: UICollectionViewCell {

    /// change it as you wish when or after initializing
    var shouldTintBackgroundWhenSelected = true

    /// you can give a special view when selected
    var specialHighlightedArea: UIView? 

    // make lightgray background display immediately(使灰背景立即出现)
    override var isHighlighted: Bool { 
        willSet {
            onSelected(newValue)
        }
    }

    // keep lightGray background until unselected (保留灰背景)
    override var isSelected: Bool { 
        willSet {
            onSelected(newValue)
        }
    }

    func onSelected(_ newValue: Bool) {
        guard selectedBackgroundView == nil else { return }
        if shouldTintBackgroundWhenSelected {
            contentView.backgroundColor = newValue ? highlightedColor : UIColor.clear
        }
        if let area = specialHighlightedArea {
            area.backgroundColor = newValue ? UIColor.black.withAlphaComponent(0.4) : UIColor.clear
        }
    }
}

extension UIColor {
    convenience init(rgb: Int, alpha: CGFloat = 1.0) {
        self.init(red: CGFloat((rgb & 0xFF0000) >> 16) / 255.0, green: CGFloat((rgb & 0xFF00) >> 8) / 255.0, blue: CGFloat(rgb & 0xFF) / 255.0, alpha: alpha)
    }
}

Simple binary logic solution. Works with Swift 3 and 4:

func collectionView(_ collectionView: UICollectionView, didSelectItemAt
    indexPath: IndexPath) {
    let cell = collectionView.cellForItem(at: indexPath) as! CategoryCell
    let lastCellColor = cell.backgroundColor
    if cell.isSelected {cell.backgroundColor = .green} else {cell.backgroundColor = lastCellColor}
}

Add all the subviews inside contentView, use backgroundView and selectedBackgroundView from UICollectionViewCell. Do not set contentView.backgroundColor.

// Add this inside your cell configuration.
private func setupSelectionColor() {
    let backgroundView = UIView()
    backgroundView.backgroundColor = .white
    self.backgroundView = backgroundView

    let selectedBackgroundView = UIView()
    selectedBackgroundView.backgroundColor = .orange
    self.selectedBackgroundView = selectedBackgroundView
}

// Add the deselection inside didSelectCallback
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    collectionView.deselectItem(at: indexPath, animated: true)
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top