skip to Main Content

I have a trouble with method "didDeselectItemAt" in collection view.
App: i have a collection with cells(see screenshot). User can to highlight only one cell.

when the user clicks on one cell, it is highlighted. when he click on another one, another one is selected, and the previous one becomes normal.

and when I select, for example, the first cell, and then the last one, the first one remains selected.
empirically, I found out that this problem. happens when, after the first one, I click on a cell that is out of view while clicking on the first one (you need to scroll)

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    if collectionView == albumsCollectionView {
        guard let cell = collectionView.cellForItem(at: indexPath) as? AlbumCollectionCell else { return }
        cell.isCellSelected()
        presenter?.didChooseAlbum(with: indexPath.row) {
            self.picturesCollectionView.reloadData()
        }}

func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
    if collectionView == albumsCollectionView {
        guard let cell = collectionView.cellForItem(at: indexPath) as? AlbumCollectionCell else { return }
        cell.isCellDeselected()
    }
}

enter image description here

2

Answers


  1. As @DonMag mentioned in the comment, you have to track selected state in your data source. Here are the steps.

    As you are showing some data in cell, you can make a model for it.

    struct Album {
        let name: String
        var isSelected: Bool
    }
    

    In AlbumCollectionCell add Album type variable. And override the isSelected property of UICollectionViewCell

    class AlbumCollectionCell: UICollectionViewCell {
    
        var album: Album? {
            didSet {
                guard let album = album else { return }
    
                if shoe.isSelected {
                    self.backgroundColor = UIColor.orange
                }
                else {
                    self.backgroundColor = UIColor.green
                }
                
               self.albumLabel.textColor = .white
               self.albumLabel.text = album.name
            }
        }
        
        override var isSelected: Bool {
            didSet {
                if self.isSelected {
                    self.backgroundColor = UIColor.orange
                }
                else {
                    self.backgroundColor = UIColor.green
                }
            }
        }
    }
    

    In UIViewController, create an array of Album type which holds the data of cells. You may assign the data in viewDidLoad() method.

    var albums = [Album]()
    

    Set allowsSelection property of UICollectionView to true.

    collectionView.allowsSelection = true
    

    In cellForItem method pass the Album data to cell.

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "AlbumCollectionCell", for: indexPath) as? AlbumCollectionCell {
            cell.album = albums[indexPath.item]
            return cell
        }
        return UICollectionViewCell()
    }
    

    Modify didSelectItem method like below.

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        albums[indexPath.item].isSelected = true
    }
    

    And override didDeselectItemAt method.

    func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
        albums[indexPath.item].isSelected = false
    }
    
    Login or Signup to reply.
  2. If you are trying to persist the selection – that is, if you want to save it and restore the last selection between app uses, or if you’re navigating away and you want to restore it when navigating again to that controller – then you need to track it with your data source.

    However, if you’re not concerned with persistence, you can let the collection view handle it.

    In your cell class, override isSelected:

    override var isSelected: Bool {
        didSet {
            contentView.backgroundColor = isSelected ? .systemOrange : .systemGreen
        }
    }
    

    Now, you don’t need any "cell appearance" code in didSelectItemAt or in didDeselectItemAt … the collection view will manage the selected state.

    Here’s a quick, complete example…

    Custom Cell class

    class LabelCollectionViewCell: UICollectionViewCell {
        let label = UILabel()
        override init(frame: CGRect) {
            super.init(frame: frame)
            commonInit()
        }
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            commonInit()
        }
        private func commonInit() {
            label.textColor = .white
            label.textAlignment = .center
            label.translatesAutoresizingMaskIntoConstraints = false
            contentView.addSubview(label)
            let g = contentView.layoutMarginsGuide
            NSLayoutConstraint.activate([
                label.topAnchor.constraint(equalTo: g.topAnchor, constant: 4.0),
                label.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
                label.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
                label.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -4.0),
            ])
            contentView.backgroundColor = .systemGreen
            contentView.layer.cornerRadius = 12
        }
        override var isSelected: Bool {
            didSet {
                contentView.backgroundColor = isSelected ? .systemOrange : .systemGreen
            }
        }
    }
    

    Example Controller class

    class SelColViewVC: UIViewController {
        
        var albumsCollectionView: UICollectionView!
        
        let myData: [String] = [
            "Favorites",
            "Recents",
            "Recently Added",
            "Something Else",
            "Five",
            "Six",
            "Seven",
            "Eight",
        ]
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            let fl = UICollectionViewFlowLayout()
            fl.scrollDirection = .horizontal
            fl.estimatedItemSize = CGSize(width: 120, height: 50)
            albumsCollectionView = UICollectionView(frame: .zero, collectionViewLayout: fl)
            albumsCollectionView.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(albumsCollectionView)
            let g = view.safeAreaLayoutGuide
            NSLayoutConstraint.activate([
                albumsCollectionView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
                albumsCollectionView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
                albumsCollectionView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
                albumsCollectionView.heightAnchor.constraint(equalToConstant: 80.0),
            ])
            
            albumsCollectionView.contentInset = UIEdgeInsets(top: 0.0, left: 8.0, bottom: 0.0, right: 8.0)
            albumsCollectionView.register(LabelCollectionViewCell.self, forCellWithReuseIdentifier: "cell")
            albumsCollectionView.dataSource = self
            albumsCollectionView.delegate = self
            
        }
        
    }
    extension SelColViewVC: UICollectionViewDataSource, UICollectionViewDelegate {
        func numberOfSections(in collectionView: UICollectionView) -> Int {
            return 1
        }
        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
            return myData.count
        }
        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
            let c = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! LabelCollectionViewCell
            c.label.text = myData[indexPath.item]
            return c
        }
    
        func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
            if collectionView == albumsCollectionView {
                presenter?.didChooseAlbum(with: indexPath.item) {
                    self.picturesCollectionView.reloadData()
                }}
            }
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search