Skip to content

Render live content in accessibility previews#340

Draft
RoyalPineapple wants to merge 3 commits into
a11y-container-graphfrom
a11y-live-preview-content
Draft

Render live content in accessibility previews#340
RoyalPineapple wants to merge 3 commits into
a11y-container-graphfrom
a11y-live-preview-content

Conversation

@RoyalPineapple
Copy link
Copy Markdown
Collaborator

Summary

  • .accessibilityPreview() now embeds the previewed content live in a ZStack instead of capturing a static UIImage of it. Spinners spin, counters tick, async loads update — anything stateful keeps running.
  • The off-screen UIImage capture was producing empty images anyway because SwiftUI's render server doesn't reliably commit in an off-screen window; this change sidesteps that entirely.
  • Adds the hierarchical container legend (from the parent PR's HierarchyLegendView) to the live-preview path when configuration.showContainers is true, matching the snapshot-test behavior.
  • Scopes the gray background to the legend area only so it doesn't bleed into the snapshot content above.

Scope

Only touches AccessibilitySnapshotView (the live-preview type). The snapshot-test path (PreParsedAccessibilitySnapshotView, SwiftUIAccessibilitySnapshotContainerView) is unaffected and continues to capture pixels through the same flow.

Tradeoff

Markers are parsed once on appear. If the previewed content's layout changes after parse (e.g. a text label widens), the overlay frame stays at the parse-time geometry. A refresh() hook or layout-change-driven re-parse is a reasonable follow-up.

Stack

Based on a11y-container-graph (#278). Merge that first.

Test plan

  • Run the demo target with BasicAccessibilityDemo().accessibilityPreview() as the root — confirm overlays sit over the live demo content
  • Same with configuration: .init(viewRenderingMode: .drawHierarchyInRect, showContainers: true) and a container demo — confirm hierarchical legend renders below
  • Confirm ProgressView inside a previewed view continues to animate

🤖 Generated with Claude Code

RoyalPineapple and others added 3 commits May 26, 2026 13:07
- Extract a shared makeSwiftUIHostingController helper in
  FBSnapshotTestCase+SwiftUI and SnapshotTesting+SwiftUI. On iOS 16.4+
  it uses safeAreaRegions = []; on earlier iOS it wraps the rootView in
  .ignoresSafeArea() so the same alignment guarantee holds across all
  supported iOS versions.
- Add HierarchyColorAssignmentTests covering the invariant that an
  element's colorIndex equals its position in flattenToElements(), plus
  container indices being sequential and independent of elements.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The parser already assigns each element a sequential `traversalIndex`
that equals its position in `flattenToElements()` (the markers array).
There's no need for a separate type to "build" a sort-and-map between
them — they're the same number.

Replace HierarchyColorAssignment + its parallel AssignedNode tree + the
build() dance with a small private helper inside HierarchyLegendView:

  - Element colorIndex comes straight from traversalIndex
  - Container colorIndex is a simple depth-first counter

HierarchyLegendView now takes [AccessibilityHierarchy] directly. Drop
HierarchyColorAssignment.swift (public type) and
HierarchyColorAssignmentTests (the invariant is now a passthrough of
the parser's contract, already covered upstream).

Net: -159 lines, one fewer public type, one fewer file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`.accessibilityPreview()` previously captured a UIImage of the content
and overlaid markers on that snapshot. The image capture produced an
empty image in the off-screen window because SwiftUI's render server
doesn't reliably commit there. Embed the content directly in a ZStack
and overlay parsed marker shapes on top — spinners spin, counters tick,
async loads update.

Markers are still parsed once on appear via the existing off-screen
hosting controller (needed for window-relative accessibilityFrame
coordinates), and their shapes are positioned in the same coordinate
space as the displayed content because both render at renderSize.

Also adds the hierarchical container legend to the live-preview path
when configuration.showContainers is true, matching the snapshot-test
behavior. If the content layout changes after parse — e.g. a text label
grows — the overlay frames stay at the parse-time geometry; a refresh
hook is left as a follow-up.

Snapshot-test path is unaffected: it still uses PreParsedAccessibility-
SnapshotView and captures pixels via SwiftUIAccessibilitySnapshot-
ContainerView. This change only touches AccessibilitySnapshotView, the
live-preview type.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@RoyalPineapple RoyalPineapple force-pushed the a11y-live-preview-content branch from 3170c21 to cf266b9 Compare May 26, 2026 13:32
@RoyalPineapple RoyalPineapple force-pushed the a11y-container-graph branch from 3e1b99a to 5cf916a Compare June 2, 2026 14:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant