ryer.io

The iOS Bug Where Every Transform Was Applied Twice

TL;DR

  • Animated icons rendered small and at the wrong angle on iOS only; Android was perfect.
  • Ruled out the tooltip container, the Reanimated wrapper, and the style prop one at a time.
  • The culprit was transform: rotation values apply twice on iOS, so 24 degrees renders as 48.
  • Using LottieView directly fixed the animation; a scale transform and resizeMode fixed the sizing.
  • It’s a workaround, not a fix. A proper repro for the Lottie team is still owed.

An entire day on one class of bug: after a library upgrade, our animated icons render smaller and rotated wrong on iOS. On Android they’re perfect. The icons are badges on the home screen, built on Lottie and wrapped in a custom AnimatedIcon that goes through React Animated’s createAnimatedComponent.

The whole day was elimination, so I’ll write it in the order that things got ruled out.

Not the animation layer

First instinct was that the animation itself was the problem. I swapped the Animated Lottie component for a standard LottieView with no animation at all. On iOS, no change — still the wrong size.

That was the most useful early result, because it split one bug into two. Whatever was happening was a styling problem, not an animation problem, and I could stop reading animation code.

Not the tooltip (mostly)

Next suspect: the icon sits inside a tooltip from React Native Elements, and its container style constrains children somewhat arbitrarily. Removing size and style props from the parent while wrapped in the tooltip didn’t clarify anything, and reinstating them unexpectedly aligned iOS and Android.

Testing the size inside and outside the tooltip settled it: the size was consistent in both places, and depended only on whether a transform was present. So the tooltip is a red herring for the sizing bug.

The tooltip does have a real, separate problem — its styling options don’t let you meaningfully change container behaviour, which I confirmed by reading its source. Worth remembering, not what I was chasing.

Not the wrapper, not the style prop

I stripped the style object down to bare essentials. Zero difference. I removed the react-native-reanimated wrapper around the Lottie component. Also no effect.

By this point everything decorative was eliminated and only one thing was left.

It’s the transform, and it doubles

Removing the style prop didn’t change sizing, but changing transform did. That’s the isolation that mattered.

Applying a 24-degree rotation produced a visible size change; scaling didn’t behave the same way. So I walked through angles — 24, 90, 180 — and the pattern was unmistakable: iOS over-rotates by exactly a factor of two. The angle doubles on iOS and stays correct on Android.

Transforms are being applied twice. That single fact explains everything I’d seen all day: the wrong angle directly, and the wrong size because a doubled rotation changes the bounding box the icon is laid out in.

I tried rearranging property application order in the custom component and forcing height and width precedence by changing the load order of style definitions. The behaviour persisted through reloads, so it isn’t caching — it’s happening inside Lottie’s native layer.

What actually helped

Use LottieView directly. Abandoning my custom AnimatedIcon wrapper in favour of calling Lottie directly resolved the animation problems immediately. A good reminder that trudging through my own abstraction layers isn’t always the way.

resizeMode matters. Reading Lottie’s source, the resizeMode variants — contain, cover, center — directly affect displayed size. Setting it to cover produced correct sizing, though only for part of the problem.

A scale transform. On reload, applying scale resolved the transform oddity. I also tried a platform check that halves rotation and translation on iOS, which works arithmetically and is exactly as ugly as it sounds. Odder still, with the platform check the icon rendered smaller above the tooltip when tapped, suggesting the tooltip may render the icon redundantly.

Where I’m leaving it

The diamond is back on track, slightly glowing with mystery. I’m not satisfied — halving a value to cancel out a doubling is a workaround that will break the moment the underlying bug is fixed.

I documented the observations in Notion and searched for existing GitHub reports without finding a match. The right next step is a bare-bones React Native project reproducing this for the Lottie maintainers, and I don’t have the time today to build one.

Two things I’m taking with me. Upgrades should be incremental with testing at each step, because a batch upgrade left me guessing which library changed the behaviour. And when a bug survives every elimination, stripping back to the core component beats adding more layers to inspect.