Sign component of `fromValue` / `toValue` ignored when animating `transform.scale.x` and `transform.scale.y` simultaneously

Originator:cal.stephens
Number:rdar://FB9862872 Date Originated:2022-1-28
Status:Closed Resolved:Yes
Product:Core Animation API Product Version:
Classification:Incorrect/Unexpected Behavior Reproducible:yes
 
When using `CABasicAnimation` to animate `transform.scale.x` and `transform.scale.y` simultaneously, the resulting animation is different from performing a single animation on `transform`. Specifically, the sign component of `fromValue` / `toValue` is ignored.

With a simple CALayer:

```swift
let textLayer = CATextLayer()
textLayer.string = "Hello world"
textLayer.foregroundColor = UIColor.white.cgColor
textLayer.backgroundColor = UIColor.blue.cgColor
textLayer.position = CGPoint(x: 200, y: 300)
textLayer.bounds.size = CGSize(width: 200, height: 200)
view.layer.addSublayer(textLayer)
```

these two equivalent CAAnimation configurations result in different animations at runtime:

(1) animateSeparately

```swift
let scaleX = CABasicAnimation(keyPath: "transform.scale.x")
scaleX.duration = 1.0
scaleX.fromValue = -0.5
scaleX.toValue = -1
scaleX.repeatCount = .greatestFiniteMagnitude
scaleX.autoreverses = true
textLayer.add(scaleX, forKey: "transform.scale.x")

let scaleY = CABasicAnimation(keyPath: "transform.scale.y")
scaleY.duration = 1.0
scaleY.fromValue = -0.5 // results in same animation as `scaleY.fromValue = 0.5`
scaleY.toValue = -1 // results in same animation as `scaleY.toValue = 1`
scaleY.repeatCount = .greatestFiniteMagnitude
scaleY.autoreverses = true
textLayer.add(scaleY, forKey: "transform.scale.y")
```

(2) singleAnimation

```swift
let transform = CABasicAnimation(keyPath: "transform")
transform.duration = 1.0
transform.fromValue = CATransform3DMakeScale(-0.5, -0.5, 1)
transform.toValue = CATransform3DMakeScale(-1, -1, 1)
transform.repeatCount = .greatestFiniteMagnitude
transform.autoreverses = true
textLayer.add(transform, forKey: "transform")
```

I expect these two CAAnimation configurations to result in the same animation at runtime, where the layer is flipped across both the x axis and y axis.

Comments

Response

Thanks for submitting this report. This behaves as expected.

There is inherent ambiguity in a transform matrix. For example, Scale(-0.5, -1, -1) * Rotation(PI, 1, 0, 0) is the same transform matrix as Scale(-0.5, 1, 1).

If scale.y is then set to -0.5, you would not get a y-flip in the first decomposition, and that’s the “issue” you observed. There’s no “fix” for this because whichever way we decide to decompose a transform matrix for merging multiple animations, one can always find a counterexample due to the ambiguity.

By cal.stephens at Feb. 28, 2022, 6:44 p.m. (reply...)

Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!