Skip to content

Repository files navigation

DynamicUI

Render server-driven SwiftUI interfaces from JSON across Apple platforms.

DynamicUI turns versioned JSON component trees into native SwiftUI views. Use it for remote forms, configurable extension interfaces, feature-driven layouts, and rapid prototypes without giving up native controls or accessibility semantics.

Supported Swift versionsSupported platformsSwift Package ManagerRun tests on macOSRun tests on iOSRun tests on visionOSRun tests on Apple TVRun tests on Watch OSLicense

DynamicUI playground rendering a JSON-defined SwiftUI interface

Why DynamicUI?

  • Native SwiftUI views on iOS, macOS, tvOS, watchOS, Mac Catalyst, and visionOS.
  • Nested layouts, interactive controls, conditional content, and runtime value updates.
  • Versioned schemas with validation before rendering.
  • Application-defined components through a custom renderer.
  • Explicit VoiceOver, Voice Control, and UI-test metadata in JSON.

Requirements

  • Swift 5.9+ (Xcode 15+)
  • iOS 15+, macOS 12+, tvOS 14+, watchOS 8+, Mac Catalyst 15+, visionOS 1.0+

Installation

Add DynamicUI using Swift Package Manager:

dependencies:[.package(url:"https://github.com/0xWDG/DynamicUI.git", exact:"0.1.1"),],targets:[.target(name:"MyTarget", dependencies:[.product(name:"DynamicUI",package:"DynamicUI"),]),]

In Xcode, select File → Add Package Dependencies, enter https://github.com/0xWDG/DynamicUI, and choose version 0.1.1.

Then import the package:

import DynamicUI

Quick start

import SwiftUI
import DynamicUI
structContentView:View{letjson=""" [ {"type": "Text","title": "This interface comes from JSON","modifiers": {"foregroundStyle":"red","opacity":0.6} }, {"type": "Button","title": "Continue","eventHandler": "continue" }, {"type": "Toggle","title": "Show details","identifier": "showDetails" } ]"""@Stateprivatevarcomponent:DynamicUIComponent?@Stateprivatevarerror:Error?varbody:someView{DynamicUI(json: json, component: $component, error: $error)}}

Versioned layouts and validation

For production payloads, use the versioned layout envelope:

{
"schemaVersion": 1,
"components": [
{ "type": "Text", "title": "A validated layout" }
]
}

Legacy top-level component arrays remain supported. Validate either representation before rendering with try DynamicUILayout(json: json). Validation rejects unsupported schema versions, empty types and identifiers, duplicate identifiers, and malformed component conditions.

Conditional content

Conditional expressions can select strings or control whether an entire component renders:

[
{
"type": "Toggle",
"title": "Show favorite",
"identifier": "favorite"
},
{
"type": "Label",
"title": "{$favorite ? Saved : Not saved}",
"url": "{$favorite ? star.fill : star}"
},
{
"type": "Text",
"title": "The favorite is enabled",
"if": "$favorite"
}
]

The string syntax is {$identifier ? valueWhenTrue : valueWhenFalse}. Missing identifiers and empty, zero, false, or null values select the false branch and hide conditional views.

Handle interactions

The component binding receives the latest interacted-with component. Stateful controls include their new value in state; identifier and eventHandler let your application route the update.

.onChange(of: component){ component inguardlet component else{return}print(component.identifier asAny)print(component.eventHandler asAny)print(component.state asAny)}

You can use a callback instead of a binding:

DynamicUI(json: json){ component inprint(component.eventHandler asAny, component.state asAny)}

Custom components

Render application-specific component types without forking DynamicUI. Return nil for component types your renderer does not recognize:

DynamicUI(
json: json,
component: $component,
error: $error,
customViewRenderer:{ component inguard component.type =="ProductCard"else{returnnil}returnAnyView(ProductCard(
title: component.title ??"Product",
productID: component.parameters?["productID"]?.toString()))})

DynamicUI applies supported modifiers and accessibility metadata to the custom view. Unknown types that neither DynamicUI nor the application recognizes are logged and skipped.

Accessibility

DynamicUI uses native SwiftUI controls and supports explicit assistive-technology metadata:

{
"type": "Button",
"title": "Save",
"accessibilityLabel": "Save profile",
"accessibilityHint": "Saves your profile changes",
"accessibilityValue": "Ready",
"accessibilityIdentifier": "profile.save",
"accessibilityInputLabels": ["Save", "Save profile"]
}

Use accessibilityHidden: true only for decorative content. Visible control titles continue to provide native semantics when explicit accessibility metadata is not needed. Accessibility string fields support the same conditional expressions as visible strings.

JSON schema

Every component requires a case-sensitive type. Common optional fields are:

FieldPurpose
titleLabel, title, placeholder, or image description
ifIdentifier condition such as $showDetails that controls rendering
identifierStable key for updates and conditional expressions
eventHandlerApplication-defined event name returned on interaction
defaultValueInitial value for stateful controls
childrenNested component array for containers
urlSF Symbol name or URL, depending on the component
disabledDisables the component
modifiersVisual and behavioral modifiers
minimumValue, maximumValueNumeric bounds for sliders and progress views
accessibilityLabelConcise, speakable name for assistive technologies
accessibilityHintDescribes the result of interacting with the component
accessibilityValueAccessible state or formatted value
accessibilityIdentifierStable identifier for UI automation
accessibilityHiddenHides decorative content from assistive technologies
accessibilityInputLabelsAlternative spoken names for Voice Control

Unknown component types are logged and skipped, allowing valid sibling components to keep rendering. Decode and validation failures are written to the optional error binding and display a fallback error view. Objects without a string type field are treated as metadata and ignored in component arrays.

Playground and documentation

The Playground directory contains an Xcode project with basic and exhaustive JSON examples for macOS, iOS, watchOS, tvOS, and visionOS.

See the complete schema, platform behavior, component examples, and modifier reference in the documentation.

Used By

Using DynamicUI in your project? Open a pull request to add it here.

Contributing

Issues and pull requests are welcome. See CONTRIBUTING.md for setup, testing, and contribution guidance, or browse the good first issue label.

Contact

🦋 @0xWDG 🐘 mastodon.social/@0xWDG 🐦 @0xWDG 🧵 @0xWDG 🌐 wesleydegroot.nl 🤖 Discord

About

Render server-driven SwiftUI interfaces from JSON across Apple platforms.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

57 stars

Watchers

3 watching

Forks

Releases

Sponsor this project

Used by

Contributors

Languages