Search code examples
iosswiftcore-animation

CABasic strokeEnd Animation is not working with customView in swift?


on calling startAnimation() the stroke Animation is not showing up, however, on using UIView.animate inside startAnimation function, it is working fine, only CABasicAnimation is not working. I am not sure what's wrong with this code. The frame is also coming fine, just the animation not showing up.

class ToastView: UIView {

lazy var label: UILabel = {
    let label = UILabel()
    label.textColor = .white
    label.font = UIFont.systemFont(ofSize: 14, weight: .regular)
    return label
}()

lazy var subLayer: CAShapeLayer = {
    let shapeLayer = CAShapeLayer()
    shapeLayer.fillColor = nil
    shapeLayer.strokeColor = UIColor.green.cgColor
    shapeLayer.lineCap = .round
    shapeLayer.lineWidth = 2
    return shapeLayer
}()

override init(frame: CGRect) {
    super.init(frame: frame)
    customInit()
}

required init?(coder: NSCoder) {
    super.init(coder: coder)
    customInit()
}

private func customInit() {
    backgroundColor = .black
    self.addSubview(label)
    label.translatesAutoresizingMaskIntoConstraints = false
    let constraints = [
        label.topAnchor.constraint(equalTo: self.topAnchor, constant: 2),
        label.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 3),
        label.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 4),
        label.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 4)
    ]
    NSLayoutConstraint.activate(constraints)

    layer.borderWidth = 1
    layer.borderColor = UIColor.white.cgColor
    layer.cornerRadius = 4
    layer.masksToBounds = true
}

func startAnimation(toastMessage: String) {

    self.label.text = toastMessage
    self.layer.insertSublayer(subLayer, at: 0)
    subLayer.frame = layer.frame
    subLayer.path = UIBezierPath(roundedRect: self.bounds, cornerRadius: 4).cgPath
    subLayer.masksToBounds = true
    
    let strokeAnimation = CABasicAnimation(keyPath: "strokeEnd")
    strokeAnimation.beginTime = 0
    strokeAnimation.fromValue = 0
    strokeAnimation.toValue = 1
    strokeAnimation.duration = 1.5
    strokeAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
    
    subLayer.add(strokeAnimation, forKey: "")
    //animate coming up with message
}

}


Solution

  • Based on your comments, you are adding your ToastView in Storyboard, with fixed dimensions.

    So, let's assume it's size is 200 x 40, centered horizontally, 12-points from the top (of the safe area), so it looks like this:

    enter image description here

    If we call your startAnimation(...) func, it ends up looking like this:

    enter image description here

    So, the first issue is that you are setting the sublayer frame like this:

    subLayer.frame = layer.frame
    

    which offsets the bezier path. It should be:

    subLayer.frame = self.bounds
    

    Now it looks like this:

    enter image description here

    If we zoom in, we can see a faint "green":

    enter image description here

    but that's not what you want. You've set layer.masksToBounds = true, so it ends up clipping the sublayer.

    If we set layer.masksToBounds = false, we get this:

    enter image description here

    and zoomed in:

    enter image description here

    Still, only slightly more green showing.

    So, let's also change to subLayer.masksToBounds = true to subLayer.masksToBounds = false:

    enter image description here

    enter image description here

    and we see the strokeEnd animation just fine.

    You didn't clarify what you're really going for... if this looks right, you should be on your way.

    Note: A view's layer border will be drawn on top of any sublayers / subviews. So if you want the Green outline to cover the White outline, you'll need to take a few more steps.