skip to Main Content

I’ve been trying to figure this out for a while, but I’ve exhausted my search. I’ve been trying to add a CAShapeLayer to a custom UICollectionViewCell, but it either doesn’t get drawn or only gets drawn sometimes. I’ve decided to simplify the problem I’m having to its bare bones with the following code:

class ViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
    
    let cellId = "cell"
    let dataSource: [UIColor] = [.blue, .purple, .brown, .green, .red]

    override func viewDidLoad() {
        super.viewDidLoad()
        collectionView.isPagingEnabled = true
        collectionView.register(CellSubclass.self, forCellWithReuseIdentifier: cellId)
    }
    
    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return dataSource.count
    }
    
    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! CellSubclass
        cell.backgroundColor = dataSource[indexPath.item]
        return cell
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: view.frame.width, height: view.frame.height)
    }
}

class CellSubclass: UICollectionViewCell {
    
    let circle = CAShapeLayer()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        circle.frame = bounds

        let circleCenter = self.center
        let path = UIBezierPath(arcCenter: circleCenter, radius: 100, startAngle: 0, endAngle: 2*CGFloat.pi, clockwise: true)
        circle.path = path.cgPath
        circle.fillColor = UIColor.gray.cgColor
        layer.addSublayer(circle)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

I have the following flow layout for the UICollectionViewController

let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
let home = ViewController(collectionViewLayout: layout)

Based on other StackOverflow questions I’ve seen, people recommend taking the approach of adding the shape sublayer to the custom UICollectionViewCell when it’s init’d like I’ve done. However, when I run this code, I get the following output:

sample

Any help with understanding how to fix this behavior so that each instance of the custom cells gets its own shape would be most appreciated.

2

Answers


  1. Change

        let circleCenter = self.center
    

    to

        let circleCenter = CGPoint(x:self.bounds.midX, y:self.bounds.midY)
    
    Login or Signup to reply.
  2. Your cell class doesn’t know its size during init().

    You’ll have better luck by setting the layer path in layoutSubviews():

    class CellSubclass: UICollectionViewCell {
        
        let circle = CAShapeLayer()
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            
            circle.fillColor = UIColor.gray.cgColor
            layer.addSublayer(circle)
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        override func layoutSubviews() {
            super.layoutSubviews()
    
            circle.frame = bounds
            let circleCenter = CGPoint(x: bounds.midX, y: bounds.midY)
            let path = UIBezierPath(arcCenter: circleCenter, radius: 100, startAngle: 0, endAngle: 2*CGFloat.pi, clockwise: true)
            circle.path = path.cgPath
        }
    }
    

    Note: this will also keep the circle centered if/when the cell size changes (such as on device rotation).

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search