Skip to content

feat: add SwiftUI rendering support#706

Open
BBC6BAE9 wants to merge 2 commits intogoogle:mainfrom
BBC6BAE9:feat/swiftui-renderer
Open

feat: add SwiftUI rendering support#706
BBC6BAE9 wants to merge 2 commits intogoogle:mainfrom
BBC6BAE9:feat/swiftui-renderer

Conversation

@BBC6BAE9
Copy link

@BBC6BAE9 BBC6BAE9 commented Feb 24, 2026

Description

Native SwiftUI renderer for the A2UI protocol, enabling all Apple platforms to render agent-generated UI from A2UI JSON payloads into native SwiftUI views.

Resolves #680.

iOS iPadOS macOS visionOS watchOS tvOS

Spec v0.8 Compliance

This renderer fully implements the A2UI spec v0.8, covering all protocol messages and component types:

  • Protocol messages: beginRendering, surfaceUpdate, dataModelUpdate, deleteSurface (+ v0.9 createSurface / updateComponents forward-compat)
  • Data binding: Path-based resolution (/items/0/name), bracket/dot normalization, template rendering, literal seeding
  • Action system: Full action context resolution — path-bound values resolved from data model at dispatch time
  • Catalog functions: formatString, formatNumber, formatCurrency, formatDate, pluralize, openUrl, required, email, regex, length, numeric, and, or, not
  • Styling: beginRendering.styles parsed into A2UIStyle, overridable per-component via SwiftUI Environment

Targeting v0.8 because all current Agent demos and web-core implementations are based on v0.8. v0.9/v0.10 support will follow as the project evolves.

18 Native Components — Platform-Adaptive via Apple HIG

All 18 standard components render using native SwiftUI controls only — no hardcoded spacing, colors, or corner radii. Each component adapts its appearance per platform rather than forcing a single mobile-style design across all devices.

Component Native Control Platform Adaptation
Text Semantic Font + AccessibilityHeadingLevel Dynamic Type on all platforms
Image AsyncImage usageHint-driven sizing (avatar→circle clip, header→full-width)
Icon SF Symbols (mapped from Material names) Font-relative sizing via custom Layout
Button .borderedProminent / .bordered System button styles per platform
TextField TextField / SecureField / TextEditor textFieldType-driven (number→.decimalPad, longText→TextEditor, obscured→SecureField)
CheckBox Toggle (.automatic style) iOS=switch, macOS=checkbox, watchOS=switch
Slider SwiftUI.Slider tvOS: fallback to ±Button pair + ProgressView
DateTimeInput DatePicker tvOS: read-only text fallback
ChoicePicker Radio / Menu / Chips / Filterable Sheet Single→radio/menu; Multi→checkbox/chips; tvOS→NavigationLink list
Tabs Picker(.segmented) / ScrollView buttons ≤5→segmented, >5→scroll; watchOS→.wheel
Modal .sheet + NavigationStack .presentationDetents on iOS/macOS; plain sheet on watchOS/tvOS
Video Singleton AVPlayerViewController One active player, poster thumbnails for others; watchOS=placeholder
AudioPlayer Custom UI + AVPlayer watchOS: AVKit unavailable, placeholder only
Row / Column HStack / VStack Spacer-based distribution layout (spaceBetween/spaceEvenly/center/end)
List ScrollView + LazyVStack/LazyHStack direction switches axis
Card Padding + background + continuous squircle No shadow (Apple system cards use background contrast)
Divider SwiftUI.Divider() Auto-adapts orientation from parent container

Platforms: iOS 17+ · macOS 14+ · tvOS 17+ · watchOS 10+ · visionOS 1+

Every platform-specific decision is documented in COMPONENT_DECISIONS.md.

Custom Component Support

Third-party components can be registered via CustomComponentRegistry and rendered inline alongside standard components:

A2UIRendererView(messages: messages)
    .environment(\.a2uiCustomComponentRenderer) { typeName, props, viewModel in
        if typeName == "Chart" {
            MyChartView(props: props)
        }
    }

The demo app includes a Rizzcharts example with custom Chart (Swift Charts doughnut) and GoogleMap (MapKit) components.

Demo Application

info action log

The demo app (samples/client/swiftui/) includes 10 pages covering both static JSON demos and live agent connections. Each demo page includes an info inspector explaining what it demonstrates, and action-triggering pages display a Resolved Action log showing the resolved context payload.

Static Demos (no agent required)

Page Description
Component Gallery All 18 standard components rendered from inline JSONL, with JSON inspector
Action Context Form with path-bound action context — fill fields, tap Send, inspect resolved payload
Format Functions formatDate catalog function in action context
Style Override Custom A2UIStyle overriding default text/button/card appearance
Custom Component Rizzcharts Chart + GoogleMap registered via CustomComponentRegistry
Incremental Update Simulates streaming surfaceUpdate messages arriving over time

Live Agent Demos (connects to repo's samples/agent/adk/ agents)

Page Agent Port
Component Gallery component_gallery :10005
Contact Lookup contact_lookup :10003
Restaurant Finder restaurant_finder :10002
Rizzcharts rizzcharts :10004

The live agent pages include A2A Client with SSE streaming and agent card discovery (/.well-known/agent-card.json).

Unit Tests — 106 tests across 7 files

Test File Tests Coverage Area
DataBindingTests 28 Path resolution, normalization, templates, nested writes, action context
MessageDecodingTests 23 Message parsing, component decoding, JSONL stream, surface lifecycle
PrimitivesTests 17 Codable round-trip for all value types (StringValue, NumberValue, etc.)
MultipleChoiceLogicTests 15 Selection modes, chip/radio/menu rendering logic, filterable
CatalogFunctionTests 10 formatString, formatNumber, formatCurrency, formatDate, pluralize
TextFieldValidationTests 7 Regex validation, empty input, edge cases
ValidationTests 6 Checks: required, email, length, and/or/not combinators

11 JSON fixtures in Tests/A2UITests/TestData/ for integration-level decoding tests.

Project Structure

renderers/swiftui/                        # Swift package (A2UI)
├── Package.swift                         # iOS 17+ / macOS 14+ / tvOS 17+ / watchOS 10+ / visionOS 1+
├── Sources/A2UI/
│   ├── A2UIRenderer.swift                # Public entry point (A2UIRendererView)
│   ├── Models/                           # Codable models
│   │   ├── Messages.swift                #   Server-to-client message types
│   │   ├── Components.swift              #   RawComponentInstance, Action, ResolvedAction
│   │   ├── ComponentTypes.swift          #   Typed properties per component (TextProperties, etc.)
│   │   ├── Primitives.swift              #   StringValue, NumberValue, BooleanValue, BoundValue
│   │   ├── AnyCodable.swift              #   Type-erased JSON value
│   │   └── DynamicKey.swift              #   CodingKey for dynamic component type dispatch
│   ├── Processing/                       # Core logic (unit-tested)
│   │   ├── SurfaceViewModel.swift        #   State manager: data binding, path resolution, action resolution
│   │   ├── SurfaceManager.swift          #   Multi-surface orchestration
│   │   ├── DataStore.swift               #   Observable per-key data model
│   │   ├── ComponentNode.swift           #   Tree builder from flat component buffer
│   │   └── JSONLStreamParser.swift       #   Streaming JSONL → message decoder
│   ├── Networking/
│   │   └── A2AClient.swift               #   A2A JSON-RPC client (SSE, agent card discovery)
│   ├── Styling/
│   │   └── A2UIStyle.swift               #   Theme parsed from beginRendering.styles
│   └── Views/
│       ├── A2UIComponentView.swift       #   Main rendering dispatcher
│       ├── A2UICustom.swift              #   Custom component rendering bridge
│       ├── CustomComponentRegistry.swift #   Registry for third-party components
│       ├── Components/                   #   18 standard components
│       │   ├── A2UIText.swift            #     Text, Image, Icon, Button, TextField,
│       │   ├── A2UIImage.swift           #     CheckBox, Slider, DateTimeInput, ChoicePicker,
│       │   ├── ...                       #     Row, Column, List, Card, Tabs, Divider,
│       │   ├── A2UIVideo.swift           #     Modal, Video, AudioPlayer
│       │   └── COMPONENT_DECISIONS.md    #     Platform-specific design rationale
│       └── Helpers/                      #   Shared utilities
│           ├── ComponentHelpers.swift     #     Bindings, alignment, layout distribution
│           ├── SVGIconView.swift          #     Custom SVG icon rendering
│           ├── SVGPathShape.swift         #     SVG path → SwiftUI Shape
│           ├── AccessibilityModifier.swift#     a11y labels/hints from component metadata
│           ├── WeightModifier.swift       #     flex-weight layout modifier
│           └── PreviewHelpers.swift       #     Xcode Preview conveniences
├── Tests/A2UITests/                      # 106 tests + 11 JSON fixtures
│   ├── DataBindingTests.swift            #   28 tests — path resolution, nested writes, action context
│   ├── MessageDecodingTests.swift        #   23 tests — message parsing, JSONL stream, surface lifecycle
│   ├── PrimitivesTests.swift             #   17 tests — Codable round-trip for all value types
│   ├── MultipleChoiceLogicTests.swift    #   15 tests — selection modes, rendering logic
│   ├── CatalogFunctionTests.swift        #   10 tests — format functions, pluralize
│   ├── TextFieldValidationTests.swift    #    7 tests — regex validation, edge cases
│   ├── ValidationTests.swift             #    6 tests — checks: required, email, length, and/or/not
│   └── TestData/                         #   11 JSON fixtures (contact_card, booking_form, etc.)
└── README.md

samples/client/swiftui/                   # Demo app
└── A2UIDemoApp/
    ├── A2UIDemoApp/                      # iOS / macOS / iPadOS / tvOS / visionOS target
    │   ├── ContentView.swift             #   Main navigation (10 demo pages)
    │   ├── Pages/                        #   ActionDemo, AgentCard, AgentChat, Catalog,
    │   │   └── ...                       #   CustomComponent, IncrementalUpdate, LiveAgent,
    │   │                                 #   Rizzcharts, Samples, StyleOverride
    │   └── Components/                   #   Rizzcharts custom renderers (Chart + Map)
    └── A2UIDemoWatchApp/                 # watchOS target

All files are new additions. No existing files on google:main were modified.

Pre-launch Checklist

Note: No CHANGELOG.md exists in the repository yet. Happy to add an entry once the project establishes a changelog format.

If you need help, consider asking for advice on the discussion board.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive and well-architected SwiftUI renderer for A2UI. The use of modern SwiftUI features like @Observable and the Layout protocol is excellent, and the extensive test suite provides great coverage. I've identified a few critical issues related to error handling where errors are silently ignored, which could make debugging difficult. I've also suggested refactoring a particularly complex function to improve its maintainability and efficiency. Addressing these points will significantly enhance the robustness of the new renderer. Additionally, please note that the PR description template should be filled out as per the repository's contributing guidelines.

@BBC6BAE9 BBC6BAE9 changed the title feat: swiftUI render feat: add SwiftUI rendering support Feb 24, 2026
@BBC6BAE9 BBC6BAE9 force-pushed the feat/swiftui-renderer branch 2 times, most recently from f3e04e6 to 73f0426 Compare February 24, 2026 19:16
@BBC6BAE9 BBC6BAE9 marked this pull request as draft February 25, 2026 09:26
@BBC6BAE9 BBC6BAE9 force-pushed the feat/swiftui-renderer branch 2 times, most recently from 0193e32 to 1a2ea60 Compare February 25, 2026 15:51
@BBC6BAE9 BBC6BAE9 marked this pull request as ready for review February 25, 2026 15:55
@BBC6BAE9 BBC6BAE9 marked this pull request as draft February 26, 2026 01:17
@BBC6BAE9 BBC6BAE9 force-pushed the feat/swiftui-renderer branch from 71513e2 to ed589dc Compare February 27, 2026 01:56
@BBC6BAE9 BBC6BAE9 marked this pull request as ready for review February 27, 2026 01:57
@BBC6BAE9 BBC6BAE9 requested a review from yjbanov as a code owner February 27, 2026 01:57
@BBC6BAE9 BBC6BAE9 force-pushed the feat/swiftui-renderer branch 2 times, most recently from 28fda9e to 0ba163b Compare February 27, 2026 11:08
@BBC6BAE9 BBC6BAE9 marked this pull request as draft February 28, 2026 18:26
@BBC6BAE9 BBC6BAE9 force-pushed the feat/swiftui-renderer branch 4 times, most recently from fe53e0e to 02a0ac3 Compare March 3, 2026 03:10
@BBC6BAE9 BBC6BAE9 force-pushed the feat/swiftui-renderer branch 2 times, most recently from a631c11 to dd7c657 Compare March 3, 2026 03:48
@BBC6BAE9 BBC6BAE9 force-pushed the feat/swiftui-renderer branch from dd7c657 to 29ed936 Compare March 3, 2026 07:26
@BBC6BAE9 BBC6BAE9 marked this pull request as ready for review March 3, 2026 07:35
@BBC6BAE9
Copy link
Author

BBC6BAE9 commented Mar 3, 2026

Hi @jacobsimionato @ava-cassiopeia — this PR implements a complete v0.8 SwiftUI renderer (18 components, data binding, JSONL streaming, multi-surface, demo app with live A2A agent). All automated review
feedback has been addressed.

Two questions:

  1. Any preferred directory structure or scope adjustments? I can split if needed.
  2. With feat: Upgrade A2UI examples to v0.9 schema #751 moving to v0.9, I can add v0.9 support in a follow-up — let me know if that should be prioritized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

Swift UI renderer

1 participant