Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 667
Travis Griggs Avatar asked Dec 03 '22 23:12

Travis Griggs


2 Answers

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
  }

}
like image 184
Hlung Avatar answered Dec 06 '22 13:12

Hlung


In the end, the only thing I've found that worked was doing the following:

override func layoutSubviews() {
    super.layoutSubviews()
    ...
    if let futureMask = self.futureShape.mask {
        let nowX = computeNowX()
        CATransaction.setDisableActions(true)
        futureMask.frame = CGRect(x: box.left + nowX, y: box.top, width: box.width - nowX, height: box.height)
        CATransaction.commit()
    }
}

Basically, I just wrapped the setting of the frame with the CATransaction calls to disable actions and forced a commit.

UPDATED

@Hlung's answer is more correct. After the setting of remove, there are two animations that have been added (as shown by printing animationKeys() at that point). I added the following extension method to capture the pattern:

extension CALayer {
    func dontAnimate(_ closure:(CALayer) -> ()) {
        closure(self)
        self.removeAllAnimations()
    }
}

This allows me to write code like:

self.someSubLayer.dontAnimate { layer in layer.frame = whatever }

A more powerful variant of dontAnimate might first snapshot the current animations, run the closure, and then removeAllAnimations() followed by restoring the snapshotted ones.

like image 35
Travis Griggs Avatar answered Dec 06 '22 13:12

Travis Griggs