setNeedsDisplay() is not updating collectionViewCell subView's drawRect

899 Views Asked by At

I am trying to display a progress indicator inside of a collectionView cell. To do this there is a background thread that sends a notification to the Main Thread to update the progress indicator.

In main view controller...

    func updateProgressIndicator(notification:NSNotification){
        let userInfo = notification.userInfo! as NSDictionary
        let date = userInfo["date"] as! NSDate
        let percentComplete = userInfo["percent"] as! Double

        self.progressIndicator.text = "\(percentComplete)%" // this works
        let dateIndex = self.calendarDates.indexOf(date)
        let indexPath = NSIndexPath(forItem: dateIndex!, inSection: 0)
        let cell = self.collectionView.dequeueReusableCellWithReuseIdentifier("DateCell", forIndexPath: indexPath) as! DateCell
        cell.showProgress()
    }

The function locates the indexPath for the cell to be updated. The cell's showProgress method is then called.

class DateCell: UICollectionViewCell {
    @IBOutlet weak var dotGraph: DotGraphView!

    func showProgress(){
        print("   DateCell showProgress") // this does get printed
        self.setNeedsDisplay()
    }

    override func drawRect(rect: CGRect) {
        print("   DateCell drawRect") // this never gets printed
        //drawing functions go here
    }
}

The showProgress() method is called for the correct cell and the print message is displayed. When the showProgress method calls setNeedsDisplay() however, the drawRect function is never executed.

The only way I've gotten the cell to update is to fully reload the cell using reloadRowsAtIndexPaths, however this should be unnecessary.

Any ideas on how to get the drawRect function to be called?

2

There are 2 best solutions below

2
Rob Napier On BEST ANSWER

You say that showProgress() is called for the correct cell, but that seems unlikely. When you call dequeueReusableCellWithReuseIdentifier(_:forIndexPath:), I would expect you to get a different instance of DateCell than the one the collection view is currently displaying. The one being displayed is in use, so it wouldn't be returned from a dequeue... method. You can test whether I'm correct about this by using NSLog on the cell. I expect the address of this one to be different than the one you returned in cellForItemAtIndexPath().

Rather than dequeuing the cell, you should just put the cell in a property so that everyone uses the same one. That's the cell you should return in cellForItemAtIndexPath() as well.

0
Dustin On

As Rob suggested, the dequeueReusableCellWithReuseIdentifier(_:forIndexPath:) was causing the problem. Storing the cell in a dictionary when it was created, allowed it to be referenced directly. This meant that the drawRect() would be called with setNeedsDisplay()

Below are the updated functions...

var calendarDateCells:[NSDate:DateCell] = [:]

func updateProgressIndicator(notification:NSNotification){
    let userInfo = notification.userInfo! as NSDictionary
    let date = userInfo["date"] as! NSDate
    let myCell = self.calendarDateCells[date]
    if(myCell != nil){
        myCell!.showProgress()
    }
}


func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier("DateCell", forIndexPath: indexPath) as! DateCell
    let date = calendarDates[indexPath.item]
    self.calendarDateCells[date] = cell
    return cell
}