Search code examples
iosswift3core-animationcalayer

Disable CALayer mask frame change animations


I have a CAShapeLayer instance with a non nil CALayer mask. I'm trying to use the frame of that mask to clip the shape. Which works fine. But when I change the frame, I don't want it to animate the frame change. I update the frame in my view's layoutSubviews and I've noticed something interesting:

override func layoutSubviews() {
    super.layoutSubviews()
    ...
    if let futureMask = self.futureShape.mask {
        "0.future position animation \(futureMask.animation(forKey: "position"))".print()
        futureMask.removeAllAnimations()
        futureMask.add(CAAnimation(), forKey: "position")
        futureMask.add(CAAnimation(), forKey: "bounds")
        "1.future position animation \(futureMask.animation(forKey: "position"))".print()
        let nowX = computeNowX()
        futureMask.frame = CGRect(x: box.left + nowX, y: box.top, width: box.width - nowX, height: box.height)
        "2.future position animation \(futureMask.animation(forKey: "position"))".print()
    }
}

The output this produces looks like:

0.future position animations Optional(<CABasicAnimation:0x174624ac0; duration = 0.25; fillMode = backwards; timingFunction = default; keyPath = position; fromValue = NSPoint: {418.94695306710298, 14}>)
1.future position animation Optional(<CAAnimation:0x170625780; duration = 0.25>)
2.future position animation Optional(<CABasicAnimation:0x170625820; duration = 0.25; fillMode = backwards; timingFunction = default; keyPath = position; fromValue = NSPoint: {418.94695306710298, 14}>)

At the beginning it's a CABasicAnimation, pointing at the position keyPath, and with a 250 millisecond duration. I blow it away (via removeAllAnimations()) and add a stub CAAnimation hoping that will do nothing. And the 1 print shows that that indeed takes place. But after I set the frame property, it has returned to the original. It seems that just setting the frame resets these animations.

Is there no way to set the frame without having these animations take place? I want other animations in the system to keep working. I just want this particular masking layer to not animate.


Solution

  • Just call layer removeAllAnimations() AFTER setting the frame.

    class GradientView: UIView {
    
      private(set) var gradientLayer: CAGradientLayer
    
      override init(frame: CGRect) {
        gradientLayer = CAGradientLayer()
        super.init(frame: frame)
        layer.insertSublayer(gradientLayer, at: 0)
      }
    
      required init?(coder aDecoder: NSCoder) {
        fatalError("not implemented")
      }
    
      override func layoutSubviews() {
        super.layoutSubviews()
        gradientLayer.frame = bounds
        gradientLayer.removeAllAnimations() // remove implicit animation from frame change
      }
    
    }