Complete the transition instead of trapping in interruptibleAnimator - #686
Open
hsoi wants to merge 2 commits into
Open
Complete the transition instead of trapping in interruptibleAnimator#686hsoi wants to merge 2 commits into
hsoi wants to merge 2 commits into
Conversation
scenee#642 replaced the fatalError() in both transitionDuration methods with a graceful return, after scenee#641 reported crashes there. The sibling interruptibleAnimator methods kept their bare fatalError(), and they trap on the same condition: UIKit running the transition with a context whose participating view controller isn't the FloatingPanelController. This is currently the top crasher in a shipping app (~110 events/week across ~95 users). It is overwhelmingly iOS 26, but reproduces on iOS 18 as well. It fires when a stack of presented controllers is torn down in one step: the panel is presented on a view controller that is itself being dismissed, so the dismiss transition runs with the outer controller as .from. Both guards now return an animator that immediately completes the transition. The nil-transitionAnimator fallbacks deliberately return a *non*-completing animator instead, because show()/hide() directly above have already scheduled completeTransition and completing it twice would be worse than the trap. Also removes a force-unwrap of fpc.transitionAnimator on the dismiss path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Covers the guard paths in both interruptibleAnimator methods: the transition completes rather than trapping when the context's participating view controller isn't a FloatingPanelController. Also pins the existing zero-duration behaviour of transitionDuration on the same condition. MockTransitionContext follows MockTransitionCoordinator in TestSupports. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Thanks for #642 — this is a follow-up to it.
#641 was titled for
interruptibleAnimator, but the code quoted in the report wastransitionDuration, so #642 fixed the methods that were quoted. BothinterruptibleAnimatormethods still have the barefatalError(), and they trap on the same condition:When it fires
A panel is presented modally on a view controller that is itself presented modally. Dismissing the outermost presentation tears the whole stack down in one step, and UIKit then runs the panel's dismiss transition with the intermediate view controller as
.from. The cast fails and the app is killed.Impact
This is currently the single highest-volume crash in a shipping iOS app. It's a latent condition that started firing at scale without any app-side change — the same unmodified binary that had been stable for weeks began crashing, which points at an OS behaviour change rather than anything in the app. It's overwhelmingly iOS 26.5.x, but a small share of occurrences are on iOS 18.x, so it isn't iOS 26-specific.
The keyboard-pinning frame appears in every sample, so a keyboard transition is consistently in flight at the same time.
The change
Both guards now return an animator that immediately completes the transition. The two
nil-transitionAnimatorfallbacks below them deliberately return a non-completing animator instead, becauseshow()/hide()directly above have already scheduledcompleteTransitionand completing twice would be worse than the trap. This also removes a force-unwrap offpc.transitionAnimatoron the dismiss path.Worst case after this is a panel that disappears without animating, rather than a terminated process.
Tests
Added to
ControllerTests, covering both guard paths, plus one pinning the existing zero-durationtransitionDurationbehaviour on the same condition.MockTransitionContextfollows the existingMockTransitionCoordinatorinTestSupports. The dismiss test also verifiescompleteTransitionis genuinely called, since a fallback that silently never completes would be worse than the crash.Left alone deliberately: the
fatalError()inPresentationController.addFloatingPanel(), wherepresentedViewControlleris the panel by construction — happy to fold it in if you'd prefer.