3

In the following minimal example, I create a UICollectionView with five UICollectionViewCells. For each, I create a CALayer with the same frame and set its backgroundColor property and add it as a sublayer to the UICollectionViewCell's layer property. The cells initially on screen are set as expected, but the remaining cells may be the wrong color, and may disappear before entirely off screen when scrolling. This question [1] suggests that this is happening because the cells are not initially on screen (?), but I don't see from the answers how to fix the problem.

Below is a minimal working example. A few things I've tried are commented out:

  1. Set the cell's background color directly, to make sure this isn't a problem with the way I set up the collection view (it isn't).
  2. Calling setNeedsDisplay() (has no effect).
  3. Removing the cell's layer's sublayers (crashes when scrolling).
import Foundation
import UIKit

enum Colors: Int {
    case Red
    case Orange
    case Yellow
    case Green
    case Blue
}

class TestViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {

    var collectionView = UICollectionView(frame: CGRect(x: 100, y: 100, width: 100, height: 100), collectionViewLayout: UICollectionViewFlowLayout())
    let reuseIdentifier = "ColorCell"

    override func viewDidLoad() {
        super.viewDidLoad()
        self.collectionView.dataSource = self
        self.collectionView.delegate = self
        self.collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: "ColorCell")
        self.view.addSubview(self.collectionView)
    }

    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 5
    }

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {

        let cell: UICollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier(self.reuseIdentifier, forIndexPath: indexPath) as UICollectionViewCell

        var l = CALayer()
        l.frame = cell.frame
        l.delegate = self

        if let color = Colors(rawValue: indexPath.item) {
            switch color {
            case .Red:
                l.backgroundColor = UIColor.redColor().CGColor
//                cell.backgroundColor = UIColor.redColor()
            case .Orange:
                l.backgroundColor = UIColor.orangeColor().CGColor
//                cell.backgroundColor = UIColor.orangeColor()
            case .Yellow:
                l.backgroundColor = UIColor.yellowColor().CGColor
//                cell.backgroundColor = UIColor.yellowColor()
            case .Green:
                l.backgroundColor = UIColor.greenColor().CGColor
//                cell.backgroundColor = UIColor.greenColor()
            case .Blue:
                l.backgroundColor = UIColor.blueColor().CGColor
//                cell.backgroundColor = UIColor.blueColor()
            }
        } else {
            l.backgroundColor = UIColor.blackColor().CGColor
//            cell.backgroundColor = UIColor.redColor()
        }

//        for sub in cell.layer.sublayers {
//            sub.removeFromSuperlayer()
//        }
        cell.layer.addSublayer(l)
//        cell.setNeedsDisplay()

        return cell

    }

}

[1] CALayer delegate method drawLayer not getting called

Community
  • 1
  • 1
sudo make install
  • 5,629
  • 3
  • 36
  • 48

1 Answers1

2

The main problem is that you should set the layer's frame to the cell's bounds, not its frame. Another problem, though, is that you will be adding additional layers when you scroll and cells are reused, since you add a sublayer every time cellForItemAtIndexPath is called. To fix those problems, I would create a subclass of UICollectionViewCell, and add and size the layer there,

class CustomCollectionViewCell: UICollectionViewCell {

    var l = CALayer()

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        l.frame = self.bounds
        layer.addSublayer(l)
    }
}

Then, cellForItemAtIndexPath becomes,

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {

        let cell = collectionView.dequeueReusableCellWithReuseIdentifier(self.reuseIdentifier, forIndexPath: indexPath) as CustomCollectionViewCell

        cell.l.delegate = self

        if let color = Colors(rawValue: indexPath.item) {
            switch color {
            case .Red:
                cell.l.backgroundColor = UIColor(red: 1, green: 0, blue: 0, alpha: 0.5).CGColor
            case .Orange:
                cell.l.backgroundColor = UIColor.orangeColor().CGColor
            case .Yellow:
                cell.l.backgroundColor = UIColor.yellowColor().CGColor
            case .Green:
                cell.l.backgroundColor = UIColor.greenColor().CGColor
            case .Blue:
                cell.l.backgroundColor = UIColor.blueColor().CGColor
            }
        } else {
            cell.l.backgroundColor = UIColor.blackColor().CGColor
        }
        return cell
    }

If you do this, be sure to register your custom class instead of UICollectionViewCell in viewDidLoad.

rdelmar
  • 103,982
  • 12
  • 207
  • 218