Search code examples
iosswiftuicollectionviewcellproperty-observer

Swift didSet on UICollectionViewCell property running, but not updating UI


I have a custom UICollectionViewCell that changes its appearance in response to selection events, and should change its appearance in response to other property changes, too, but doesn't.

class NumberCell: UICollectionViewCell {
    let numberLabel = UILabel()

    override var selected: Bool {
        didSet {
            // Updates as expected
            contentView.backgroundColor = self.selected ? UIColor.redColor() : UIColor.clearColor()
        }
    }

    var number: Int? {
        didSet {
            // Sets and shows the text in the number label as expected when cell is first initialised
            if let number = number {
                numberLabel.text = String(number)
            }
        }
    }

    var isCrossedOut: Bool = false {
        didSet {
            // Sets and displays correct values on initialisation, but later
            // stops updating display
            contentView.backgroundColor = self.isCrossedOut ? UIColor.blackColor() : UIColor.clearColor()
        }
    }

    // ...
}

The selected state for the cell updates nicely, but whenever I do cell.isCrossedOut = true, I can see the code running, but I don't see the background colour actually changing, even though it seems to be using exactly the same logic as the selection property observer.

I can trigger a visual update by doing collectionView.reloadData() (not acceptable to reload the entire collection view), or collectionView.reloadItemsAtIndexPaths([...]) (more or less acceptable I guess), but I really would prefer to update the UI dynamically.

EDIT

This is how I update the crossed out property:

func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
    if shouldBeCrossedOut(indexPath) {
        let cell = self.collectionView(collectionView, cellForItemAtIndexPath: indexPath) as! NumberCell
        cell.isCrossedOut = true
    }
}

Solution

  • Replace:

    self.collectionView(collectionView, cellForItemAtIndexPath: indexPath) as! NumberCell
    

    With:

    self.collectionView?.cellForItemAtIndexPath(NSIndexPath(forItem: 0, inSection: 0)) as! NumberCell
    

    self.collectionView(collectionView, cellForItemAtIndexPath: indexPath) as! NumberCell is the datasource method you override to supply the collection view with a cell (i.e. it will always return a newly instantiated or a newly dequeued cell, and thus one that is not currently on screen). self.collectionView?.cellForItemAtIndexPath(NSIndexPath(forItem: 0, inSection: 0)) as! NumberCell is an instance method that will return a reference to the current cell at a given index path. Storyboards are not relevant to this problem. You can view a code for a playground with effective code with no IB whatsoever here.