I have a tableview cell with a reuse-identifier set in storyboard. After checking some conditions I want to draw different things on the contentView of the cell. My problem is when the tableview re-uses the cell, the CAShapeLayer from another cell persists, so it's showing the new drawing over the (re-used) drawing.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"StatusCell"];
NSManagedObject *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
if ([[object valueForKey:@"status"] isEqualToString:@"sending"]) {
CAShapeLayer *progressLayer;
//...Configure and draw Layer with UIBezierPath
[cell.contentView.layer addSublayer:progressLayer];
} else if ([[object valueForKey:@"status"] isEqualToString:@"sent"]) {
CAShapeLayer *sentLayer;
//...Configure and draw Layer with UIBezierPath
[cell.contentView.layer addSublayer:sentLayer];
}
return cell;
}
So I tried to give the CAShapeLayer a name and delete the layer from the re-used cell before creating a new CAShapeLayer.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"StatusCell"];
NSManagedObject *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
for (CALayer *layer in cell.contentView.layer.sublayers) {
if ([layer.name isEqualToString:@"sending"] || [layer.name isEqualToString:@"sent"]) {
[layer removeFromSuperlayer];
}
}
if ([[object valueForKey:@"status"] isEqualToString:@"sending"]) {
CAShapeLayer *progressLayer;
progressLayer.name = @"sending";
//...Configure and draw Layer with UIBezierPath
[cell.contentView.layer addSublayer:progressLayer];
} else if ([[object valueForKey:@"status"] isEqualToString:@"sent"]) {
CAShapeLayer *sentLayer;
sentLayer.name = @"sent";
//...Configure and draw Layer with UIBezierPath
[cell.contentView.layer addSublayer:sentLayer];
}
return cell;
}
But with this method I get an exception when scrolling the tableview CALayerArray was mutated while being enumerated
Can someone point me in the right direction how to do it the correct way?
I would advise you to subclass UITableViewCell
, assign these layers as properties, and manage their visibility using hidden
property of CALayer
.
Create a custom cell class
@interface StatusCell: UITableViewCell
@property (nonatomic, strong) CAShapeLayer *progressLayer;
@property (nonatomic, strong) CAShapeLayer *sentLayer;
@end
Assign StatusCell
class name to your cell prototype in storyboard.
Create the layers – as you could notice, cellForRowAtIndexPath
method is not the best place to do it, as it is called often during cell life cycle. I usually create properties in init
methods, as your cell is storyboard-based, you can also use the awakeFromNib
method, it is also called only once during nib-based cells' life cycle.
@implementation StatusCell
- (void)awakeFromNib {
[super awakeFromNib];
CAShapeLayer *progressLayer;
// configuraion
progressLayer.hidden = YES;
self.progressLayer = progressLayer;
// repeat for sentLayer.
}
@end
In your cellForRow method you set one layer to be hidden, and other to be visible. You can adjust layers' positions, if required.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
StatusCell *cell = [tableView dequeueReusableCellWithIdentifier:@"StatusCell"];
// Optionally adjust your layers' frames if your cell has dynamic height.
NSManagedObject *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.progressLayer.hidden = ![[object valueForKey:@"status"] isEqualToString:@"sending"];
cell.sentLayer.hidden = ![[object valueForKey:@"status"] isEqualToString:@"sent"];
return cell;
}