fix(ios): re-apply loop animations on window re-attach#43
Merged
Conversation
iOS removes CAAnimations from a layer when the view leaves the window hierarchy. With react-navigation tabs (which detach inactive screens by default), this dropped any in-flight loop animations and they never restarted on tab return — resolves #42. Track in-flight loop animations in a per-view dictionary with an explicit beginTime, and re-add them in didMoveToWindow when the view re-attaches. Phase is preserved through the addAnimation copy, so the animation looks as if it kept running while the view was off-screen. Adds an Issues section to the example app with a tabs reproducer at example/app/issues/42/, splits the home list into API / Demos / Issues top-level tabs, and documents the reproducer pattern in AGENTS.md so future bug fixes ship with a regression test.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Resolves #42. iOS removes CAAnimations from a layer when the view leaves the window hierarchy. With
react-navigationbottom tabs (which detach inactive screens by default viareact-native-screens), this dropped any in-flight loop animations and they never restarted when the user returned to the tab — the loop appeared frozen until some unrelated re-render eventually triggered a new prop diff.EaseViewnow keeps a per-view dictionary of in-flight loop animations with an explicitbeginTime, and re-adds them indidMoveToWindowwhenever the view re-attaches to a window. iOS preservesbeginTimethroughaddAnimation's internal copy, so the loop's phase continues seamlessly via(currentMediaTime - beginTime) mod period— visually it looks as if the animation kept running the whole time the view was off-screen, rather than restarting from frame 0.I considered literally snapshotting
presentationLayerat detach and resuming mid-cycle, but it requires chained CAAnimations or a CAKeyframeAnimation per property and per direction (forreverse), and the user-visible result is indistinguishable frombeginTimepreservation for the typical sub-second tab switch.Out of scope: the same root cause applies on app foreground (after backgrounding). That uses a different hook (
UIApplicationDidBecomeActiveNotification) and can be fixed in a follow-up.Example app changes
Issuessection pattern: each fixed bug gets its own self-contained reproducer atexample/app/issues/<n>/so we can verify the fix and catch regressions later. The first one —app/issues/42/— is aTabslayout with a Loop tab (spin + pulse) and an Other tab (scrollview), matching the repro steps in react navigation stops loop animations #42.AGENTS.mdnow documents the reproducer pattern so future bug fixes ship with one.Test Plan
iOS simulator (iPhone 16 Pro, iOS 26 toolchain), Debug build. Same scripted navigation in both clips: open Issue #42 reproducer, switch Loop → Other → Loop, twice.
Before:
before.mp4
After:
after.mp4
The
mount #Nindicator stays the same across the round-trips — confirms theEaseViewis still mounted; the bug is purely the dropped CAAnimations, not a remount.CI:
yarn format:write && yarn lint && yarn test— clean (82/82 passing)