CABasicAnimation in uitableviewcell doesn't seem to work

1.8k views Asked by At

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;
}
3

There are 3 answers

1
flynn On

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.

0
thus On

The question was asked more than 5 years ago but still someone might use this answer so just do the following on UITableViewCell. This should work.

override func layoutSubviews() {
    super.layoutSubviews()
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
        self.doAnimation()
    }
}
0
Fattie On

Note that iOS has a known problem with UILabel:

you have to set the bg color of UILabel to clear, before you can fool with the color or animate it.

It's just one of those things about iOS.

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!