Commit 87827cb
authored
fix(ios): re-apply layer.transform after super.invalidateLayer (#41)
`-[RCTViewComponentView invalidateLayer]` re-applies the View's style
layer-properties to the underlying CALayer — including transform. Since
EaseView's JS shim strips `style.transform` whenever `animate` includes
any transform component, super resets `layer.transform = identity` on
every invalidateLayer call. The override here re-applied opacity,
cornerRadius, backgroundColor, border, and shadow — but missed transform.
For static or already-settled views this was invisible. Views in the
middle of a transform CAAnimation hit it as a hard-to-reproduce visual
bug:
1. updateProps' "subsequent updates" branch sets `layer.transform =
targetTransform` and queues a CABasicAnimation with
`fromValue=current, toValue=target, fillMode=removed`.
2. Mid-animation, a sibling re-render or layout settle triggers
`invalidateLayer` on this view. `[super invalidateLayer]` resets
`layer.transform` to identity from the empty style.transform.
3. The override re-applies other animated properties but leaves the
model layer's transform at identity.
4. The CAAnimation finishes. With `fillMode=removed`, presentation
reverts to model — which is now identity. The view snaps to its
un-transformed position/scale.
Backgrounding and reopening the app worked around the bug because
foregrounding triggers a window-wide displayIfNeeded after the
animation has long since completed; at that point the override's
re-apply branch executes with no in-flight animation and the model can
be corrected.
This was first surfaced in a hero ↔ list morphing card with FlashList +
LayoutAnimation, where mid-flight invalidateLayer calls during the
layout settle were the trigger.
The fix:
* Add `kMaskAnyTransform` to the early-return gate so transform-only
animated views still hit the re-apply block.
* Re-apply `self.layer.transform = [self targetTransformFromProps:...]`
unconditionally inside the existing `setDisableActions:YES`
CATransaction.
The re-apply is intentionally NOT gated on "no animation in flight". A
running CABasicAnimation interpolates the *presentation* layer between
its own fromValue/toValue and ignores the model layer for its lifetime.
With actions disabled, writing to the model does not start an implicit
animation. The model needs to be at target so that when the explicit
animation removes itself (fillMode=removed), presentation reverts to the
correct resting state — not identity.
Verified on RN 0.85.1 / Fabric / iOS 17 sim and iPhone (real device).1 parent e3621ba commit 87827cb
1 file changed
Lines changed: 19 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1162 | 1162 | | |
1163 | 1163 | | |
1164 | 1164 | | |
1165 | | - | |
1166 | | - | |
| 1165 | + | |
| 1166 | + | |
| 1167 | + | |
| 1168 | + | |
| 1169 | + | |
| 1170 | + | |
| 1171 | + | |
| 1172 | + | |
| 1173 | + | |
| 1174 | + | |
| 1175 | + | |
| 1176 | + | |
1167 | 1177 | | |
1168 | 1178 | | |
1169 | 1179 | | |
1170 | 1180 | | |
1171 | 1181 | | |
1172 | 1182 | | |
1173 | | - | |
| 1183 | + | |
| 1184 | + | |
1174 | 1185 | | |
1175 | 1186 | | |
1176 | 1187 | | |
| 1188 | + | |
| 1189 | + | |
1177 | 1190 | | |
1178 | 1191 | | |
| 1192 | + | |
| 1193 | + | |
| 1194 | + | |
1179 | 1195 | | |
1180 | 1196 | | |
1181 | 1197 | | |
| |||
0 commit comments