fix: restore accessibilityViewIsModal after nested modal dismiss#8
Open
RoyalPineapple wants to merge 3 commits intomainfrom
Open
fix: restore accessibilityViewIsModal after nested modal dismiss#8RoyalPineapple wants to merge 3 commits intomainfrom
RoyalPineapple wants to merge 3 commits intomainfrom
Conversation
9b5608c to
f0857c0
Compare
robmaceachern
approved these changes
Mar 31, 2026
Demonstrates that when a modal is presented on top of another modal and then dismissed, the remaining modal's ContainerView is left with accessibilityViewIsModal = false. This causes VoiceOver and accessibility parsers to expose elements from both the modal and the parent screen simultaneously. This test currently fails — the fix follows in the next commit. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three issues caused the remaining modal's ContainerView to be left with accessibilityViewIsModal = false after a nested modal dismisses: 1. The flag assignment used allPresentations (includes exiting presentations) instead of presentations(includeExiting: false). 2. The flag assignment was gated behind an early-return guard that bails when the top layer hasn't changed — which is exactly the case when dismissing back to the same top modal. 3. remove(presentation:) never re-evaluated the flag after removing the exiting presentation from allPresentations. Fix: move the flag assignment above the guard so it runs unconditionally, use the filtered presentation list, and call updateAccessibilityViewIsModal() from remove(presentation:). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
f0857c0 to
7ffe995
Compare
Co-Authored-By: Claude Opus 4.6 (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.
Summary
When a modal is presented on top of another modal and then dismissed, the remaining modal's
ContainerViewis left withaccessibilityViewIsModal = false. This causes VoiceOver to expose elements from both the modal and the underlying parent screen — doubling every element visible to assistive technology.Before fix — after dismissing a nested modal:
accessibilityViewIsModalon the remaining modal'sContainerViewisfalseAfter fix:
accessibilityViewIsModalis correctly restored totrueRoot cause
Three issues in
ModalPresentationViewController:1. Flag assignment included exiting presentations
updateAccessibilityViewIsModal()usedallPresentations.map(\.containerView)which includes presentations in.pendingExitand.exitingstates. The exiting presentation'sContainerViewbecomes.lastand receives the flag, while the remaining presentation's container is set tofalse.2. Early-return guard prevented re-evaluation
When the nested modal dismisses, the top accessibility layer returns to the same presentation that was on top before. The guard bails, skipping the flag assignment.
3.
remove(presentation:)didn't re-evaluate the flagAfter the exit animation completes,
remove(presentation:)removes the presentation fromallPresentationsbut never callsupdateAccessibilityViewIsModal(). The remaining container keeps itsfalsevalue permanently.Fix
presentations(includeExiting: false)to exclude exiting presentationsupdateAccessibilityViewIsModal()fromremove(presentation:)to restore the flag after removalCommits
test:Adds a failing test that reproduces the bug — presents modal A, presents modal B on top, dismisses B, assertsaccessibilityViewIsModalistrueon A'sContainerView. Fails without the fix.fix:Makes the test pass. 46/46 unit tests green.Reproduction
ContainerViewnow hasaccessibilityViewIsModal = falseTest plan
accessibilityViewIsModal = false)accessibilityViewIsModal = true)🤖 Generated with Claude Code