gizmodo gizmodo - 2 months ago 25
Swift Question

UITableViewCell Loading Wrong CAShapeLayer Shapes

I have a custom cell that has a

CAShapeLayer
to draw an arrow shape. Different arrows (up, down, right) gets displayed properly in the proper cells. Changing the arrow shape when a cell is selected works as intended.

Issue:

When the arrows are changed for displayed cells (when a cell is selected), off-screen cells' arrows seem to inherit that behavior randomly, ending in random arrow directions being rendered in the off-screen cells. Rest of the elements for off-screen cells seem to render properly.

Table View Controller:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let currentCellDescriptor = getCellDescriptorForIndexPath(indexPath)
let cell = tableView.dequeueReusableCellWithIdentifier(currentCellDescriptor["cellIdentifier"] as! String, forIndexPath: indexPath) as? CustomCell
let settings = NSUserDefaults.standardUserDefaults()
if currentCellDescriptor["cellIdentifier"] as! String == "idCellNormal" {
if let settingName = currentCellDescriptor["settingsKey"] as? String {
if let labelString = settings.stringForKey(settingName) {
cell?.headerRightLabel?.text = labelString
}
else {
cell?.headerRightLabel?.text = currentCellDescriptor["settingsValue"] as? String
}
if (currentCellDescriptor["isExpanded"] as? Bool == false) {
cell?.drawArrow(arrowDirection: "Down")
}
else {
cell?.drawArrow(arrowDirection: "Up")
}
}
else {
cell?.headerRightLabel?.text = ""
cell?.drawArrow(arrowDirection: "Right")
}
if let secondaryTitle = currentCellDescriptor["secondaryTitle"] {
cell?.headerLeftLabel?.text = secondaryTitle as? String
}
}
else if currentCellDescriptor["cellIdentifier"] as! String == "idCellValuePicker" {
cell?.textLabel?.text = currentCellDescriptor["primaryTitle"] as? String
}
cell?.delegate = self
return cell!
}

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
(self.tableView.cellForRowAtIndexPath(indexPath) as? CustomCell)?.animateAccesoryView()
}


Custom Cell Class:

class CustomCell: UITableViewCell {

// MARK: IBOutlet Properties

@IBOutlet weak var textField : UITextField!
@IBOutlet weak var headerLeftLabel : UILabel?
@IBOutlet weak var headerRightLabel : UILabel?
@IBOutlet weak var rightAccessoryView : UIView?

var arrowShape = CAShapeLayer()
var arrowDownPath = UIBezierPath()
var arrowUpPath = UIBezierPath()
var arrowRightPath = UIBezierPath()

var delegate: CustomCellDelegate!



func drawArrow(arrowDirection direction : String) {
//Draw Accessory Arrow

// Create CAShapeLayerS
self.arrowShape.bounds = CGRect(x: 0, y: 0, width: 32, height: 32)
self.arrowShape.position = CGPointMake(((rightAccessoryView?.frame.width)!/2), (rightAccessoryView?.frame.height)!/2)

self.rightAccessoryView?.layer.addSublayer(self.arrowShape)

//Arrow Down Path
arrowDownPath.moveToPoint(CGPoint(x: 9, y: 13))
arrowDownPath.addLineToPoint(CGPoint(x: 16, y: 19))
arrowDownPath.addLineToPoint(CGPoint(x: 23, y: 13))
arrowDownPath.addLineToPoint(CGPoint(x: 16, y: 19))
arrowDownPath.addLineToPoint(CGPoint(x: 9, y: 13))
arrowDownPath.closePath()

//Arrow Up Path
arrowUpPath.moveToPoint(CGPoint(x: 9, y: 19))
arrowUpPath.addLineToPoint(CGPoint(x: 16, y: 13))
arrowUpPath.addLineToPoint(CGPoint(x: 23, y: 19))
arrowUpPath.addLineToPoint(CGPoint(x: 16, y: 13))
arrowUpPath.addLineToPoint(CGPoint(x: 9, y: 19))
arrowUpPath.closePath()

//Arrow Up Path
arrowRightPath.moveToPoint(CGPoint(x: 13, y: 9))
arrowRightPath.addLineToPoint(CGPoint(x: 19, y: 16))
arrowRightPath.addLineToPoint(CGPoint(x: 13, y: 23))
arrowRightPath.addLineToPoint(CGPoint(x: 19, y: 16))
arrowRightPath.addLineToPoint(CGPoint(x: 13, y: 9))
arrowRightPath.closePath()

//set Arrow Shape (CAShapeLayer)

switch direction{
case "Down":
arrowShape.path = arrowDownPath.CGPath
case "Up":
arrowShape.path = arrowUpPath.CGPath
case "Right":
arrowShape.path = arrowRightPath.CGPath
default:
arrowShape.path = arrowDownPath.CGPath
}

arrowShape.strokeColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.2).CGColor
arrowShape.lineWidth = 1.5
arrowShape.lineJoin = kCALineJoinRound
arrowShape.lineCap = kCALineCapRound
arrowShape.strokeEnd = 0.5
}

func animateAccesoryView (){
let animation = CABasicAnimation(keyPath: "path")
if (self.arrowShape.path == arrowDownPath.CGPath) {
animation.toValue = arrowUpPath.CGPath
}
else {
animation.toValue = arrowDownPath.CGPath
}
animation.duration = 0.3
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animation.fillMode = kCAFillModeBoth
animation.removedOnCompletion = false
self.arrowShape.addAnimation(animation, forKey: animation.keyPath)
}

Answer

I was able to fix this by using prepareForReuse in Table View Cell subclass. This is also available for UICollectionViewCell.

override func prepareForReuse() {
    super.prepareForReuse()
    self.arrowShape.removeAllAnimations()
    self.arrowShape.removeFromSuperlayer()
}
Comments