I'm trying to make a label within a tableview cell change background color with a CABasicAnimation and it doesn't seem to be working - the cell background color remains solid with no animation.
This code is in the cellForRowAtIndexPath method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MainCellTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
NSString *name = @"Hello";
UIColor *color = [UIColor colorWithRed:0.0f/255.0f green:100.0f/255.0f blue:200.0f/255.0f alpha:1.0f];
// I'm setting the label as a strong property of the cell
cell.label = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 100.0f, cell.contentView.frame.size.height)];
cell.label.text = name;
cell.label.textColor = [UIColor whiteColor];
cell.label.backgroundColor = color;
[cell.contentView addSubview:cell.label];
UIColor *endColor = [UIColor redColor];
CABasicAnimation *animation;
animation=[CABasicAnimation animationWithKeyPath:@"backgroundColor"];
animation.duration=0.7;
animation.repeatCount=HUGE_VALF;
animation.autoreverses=YES;
animation.fromValue=(id)color.CGColor;
animation.toValue=(id)endColor.CGColor;
[cell.label.layer addAnimation:animation forKey:@"pulses"];
return cell;
}
I figured out the problem, though I'm not sure why it is the case. If somebody else can add to this answer, please feel free.
It looks as though setting the background color of the cell label was hiding the animation of the layer. If I comment out the backgroundColor setting for the label OR use cell.label.layer.backgroundColor, it works.
What confuses me is that outside of the context of the cell, for instance if you just set a label within a regular view, you can set the backgroundColor and still see the animation.
The problem seems to be with UILabel.
It seems that, annoyingly,
you have to set the bg color of UILabel to clear, before you can fool with the color or animate it.
The following works great:
Small text badge, where the background color throbs, throbs, throbs:
In storyboard, simply set the bg color to say black just so you can see what you're doing. (The IBDesignable system is not good enough, as of writing, to render the bounce animation in storyboard.)
Naturally, you can add an @IBInspectable just to set the color bounce - but why when red/orange is so good!? :)
@IBDesignable class ColorTextBadge: UILabel {
override var text: String? {
didSet {
print("Text changed from \(oldValue) to \(text)")
// very often, you'll want to change the color or whatever
// when this happens
}
}
override init(frame: CGRect) {
super.init(frame: frame)
initialSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialSetup()
}
func initialSetup() {
// this will only happen ONCE
// annoyingly, you have to, in a word, do this to make color bg animations work, on a UILabel:
backgroundColor = UIColor.clear
// also, shape the corners...easy
let r = self.bounds.size.height / 2
let path = UIBezierPath(roundedRect: self.bounds, cornerRadius:r)
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
}
override func layoutSubviews() {
super.layoutSubviews()
doAnimation()
// you will have to restart the animation EACH TIME
// the label is shaped by layout.
}
func doAnimation() {
layer.removeAnimation(forKey: "bounce")
let bounce = CABasicAnimation(keyPath: "backgroundColor")
bounce.fromValue = sfRed.cgColor
bounce.toValue = UIColor.orange.cgColor
// core magic:
let ct = CACurrentMediaTime().truncatingRemainder(dividingBy: 1)
bounce.timeOffset = ct
bounce.duration = 0.5
bounce.autoreverses = true
bounce.repeatCount = Float.greatestFiniteMagnitude
bounce.isRemovedOnCompletion = false
layer.add(bounce, forKey: "bounce")
}
}
There's more!
When table view cells disappear in iOS, any layer animations are killed - boo!
Unfortunately there is only one way to deal with this. Honest, it's the only way. (.isRemovedOnCompletion does not help here.)
In your table view, add
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if let c = cell as? ActivityCell {
c.cellWillDisplaySignalling()
}
}
Then in your cell class,
class YourCell: UITableViewCell {
@IBOutlet var blah: UILabel!
@IBOutlet var blah: UILabel!
@IBOutlet var aBadge: ColorTextBadge!
@IBOutlet var anotherBadge: ColorTextBadge!
...
override func cellWillDisplaySignalling() {
aBadge.doAnimation()
anotherBadge.doAnimation()
}
}
That is, unfortunately, the only way for now for a cell to know it is appearing.
Simply call the "doAnimation"s in that function.
Finally, syncing the pulses!
Look in doAnimation at the two lines core magic. I can't be bothered explaining, but try it with and without that; it looks shoddy unless it is synced!