Search code examples
swiftuicollectionviewcellcalayershadowcashapelayer

Invalidate and Redraw CAShapeLayer inside collectionViewCell after device orientation change


I have a custom collection view cell that is adding a dashed border to the cell layer with the following code:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = appDealCollectionView.dequeueReusableCell(withReuseIdentifier: "appDealCollectionCell", for: indexPath) as! AppDealCollectionViewCell
    if let codes = discountCodes {
        cell.discountCodeTitle.text = codes[indexPath.row].codeMessageOne
        cell.discountCode.text = codes[indexPath.row].code

        let yourViewBorder = CAShapeLayer()
        yourViewBorder.strokeColor = UIColor.black.cgColor
        yourViewBorder.lineWidth = 2
        yourViewBorder.lineDashPattern = [10, 10]
        yourViewBorder.frame = cell.bounds
        yourViewBorder.fillColor = nil
        yourViewBorder.path = UIBezierPath(roundedRect: cell.bounds, cornerRadius: 6).cgPath
        cell.layer.addSublayer(yourViewBorder)
    }
    return cell
}

This code works perfectly fine on the initial loading of the view. However, when the orientation changes, the cell size changes. The above code does correctly draw the new border CAShapeLayer, but the previously drawn border layer is still present which were drawn based on the old size.

The result is two different border layers being present at the same time overlapping each other with different dimensions.

How do I invalidate any previously drawn CAShapeLayers? Where is the invalidation done? In cellForItemAt? Or possibly inside the custom "AppDealCollectionViewCell" itself?


Solution

  • Since cells are reusable, every call of cellForRowAtIndexPath will add another instance of CAShapeLayer onto cell. That is why you are having several borders overlapping each other. Also CALayer doest not support neither auto layout nor autoresizingMask, so you have to update size of your CAShapeLayer manually.

    You should create subclass of UITableViewCell, then create instance of CAShapeLayer and store pointer to it in class property variable. Once layout cycle occurs, in layoutSubviews function you need to update frame of CAShapeLayer.

    The final implementation looks like that:

    class BorderedTableViewCell: UITableViewCell {
    
        lazy var borderLayer = CAShapeLayer()
    
        override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
            super.init(style: style, reuseIdentifier: reuseIdentifier)
            setupBorderLayer()
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            setupBorderLayer()
        }
    
        private func setupBorderLayer() {
            borderLayer.strokeColor = UIColor.black.cgColor
            borderLayer.lineWidth = 2
            borderLayer.fillColor = nil
            borderLayer.lineDashPattern = [10, 10]
            layer.addSublayer(borderLayer)
        }
    
        private func updateBorderLayer() {
            borderLayer.frame = bounds
            borderLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 6).cgPath
        }
    
        override func layoutSubviews() {
            super.layoutSubviews()
            updateBorderLayer()
        }
    }
    

    I hope this helps.