├── .github
├── FUNDING.yml
└── workflows
│ ├── test-macOS.yml
│ ├── build-iOS.yml
│ ├── build-tvOS.yml
│ ├── build-visionOS.yml
│ ├── build-watchOS.yml
│ └── build-documentation.yml
├── .spi.yml
├── Playground
├── DynamicUI Playground
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── Preview Content
│ │ └── Preview Assets.xcassets
│ │ │ └── Contents.json
│ ├── DynamicUI_PlaygroundApp.swift
│ ├── DynamicUI_Playground.entitlements
│ ├── Storyboard.storyboard
│ └── ContentView.swift
├── DynamicUI Watch App
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── DynamicUIApp.swift
│ └── ContentView.swift
├── DynamicUI-Watch-App-Info.plist
└── DynamicUI Playground.xcodeproj
│ ├── xcshareddata
│ └── xcschemes
│ │ ├── DynamicUI Playground.xcscheme
│ │ └── DynamicUI Watch App.xcscheme
│ └── project.pbxproj
├── Sources
└── DynamicUI
│ ├── PrivacyInfo.xcprivacy
│ ├── DynamicUI.docc
│ ├── Views
│ │ ├── Divider.md
│ │ ├── Text.md
│ │ ├── TextEditor.md
│ │ ├── Image.md
│ │ ├── Button.md
│ │ ├── Gauge.md
│ │ ├── Label.md
│ │ ├── Toggle.md
│ │ ├── List.md
│ │ ├── HStack.md
│ │ ├── VStack.md
│ │ ├── ZStack.md
│ │ ├── HSplitView.md
│ │ ├── ScrollView.md
│ │ ├── TextField.md
│ │ ├── VSplitView.md
│ │ ├── NavigationView.md
│ │ ├── Group.md
│ │ ├── GroupBox.md
│ │ ├── SecureTextField.md
│ │ ├── Section.md
│ │ ├── DisclosureGroup.md
│ │ ├── ProgressView.md
│ │ ├── Picker.md
│ │ └── Form.md
│ └── Modifiers.md
│ ├── Extensions
│ ├── Binding.onChange.swift
│ └── View.modifiers.swift
│ ├── Views
│ ├── DynamicVStack.swift
│ ├── DynamicZStack.swift
│ ├── DynamicTEMPLATE.swift
│ ├── DynamicScrollView.swift
│ ├── DynamicNavigationView.swift
│ ├── DynamicLabel.swift
│ ├── DynamicVSplitView.swift
│ ├── DynamicImage.swift
│ ├── DynamicSecureTextField.swift
│ ├── DynamicTextField.swift
│ ├── DynamicTextEditor.swift
│ ├── DynamicPicker.swift
│ ├── DynamicSpacer.swift
│ ├── DynamicButton.swift
│ ├── DynamicDivider.swift
│ ├── DynamicProgressView.swift
│ ├── DynamicText.swift
│ ├── DynamicDisclosureGroup.swift
│ ├── DynamicGroup.swift
│ ├── DynamicForm.swift
│ ├── DynamicGroupBox.swift
│ ├── DynamicList.swift
│ ├── DynamicGauge.swift
│ ├── DynamicSlider.swift
│ ├── DynamicHStack.swift
│ ├── DynamicSection.swift
│ ├── DynamicToggle.swift
│ └── DynamicHSplitView.swift
│ ├── DynamicUIComponent.swift
│ ├── Helpers
│ ├── DynamicUIHelper.swift
│ └── AnyCodable.swift
│ └── DynamicUI.swift
├── .swiftlint.yml
├── Tests
└── DynamicUITests
│ └── DynamicUITests.swift
├── Package.swift
├── LICENCE.md
├── .gitignore
└── README.md
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: 0xWDG
2 |
--------------------------------------------------------------------------------
/.spi.yml:
--------------------------------------------------------------------------------
1 | version: 1
2 | builder:
3 | configs:
4 | - documentation_targets: [DynamicUI]
5 |
--------------------------------------------------------------------------------
/Playground/DynamicUI Playground/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Playground/DynamicUI Watch App/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Playground/DynamicUI Playground/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Playground/DynamicUI-Watch-App-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Playground/DynamicUI Playground/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Playground/DynamicUI Watch App/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/.github/workflows/test-macOS.yml:
--------------------------------------------------------------------------------
1 | name: Run tests on macOS
2 | on:
3 | pull_request:
4 |
5 | jobs:
6 | test_macOS:
7 | runs-on: macos-latest
8 | steps:
9 | - uses: actions/checkout@v3
10 |
11 | - name: Swift test
12 | run: swift test
13 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/PrivacyInfo.xcprivacy:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSPrivacyTracking
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Playground/DynamicUI Watch App/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "watchos",
6 | "size" : "1024x1024"
7 | }
8 | ],
9 | "info" : {
10 | "author" : "xcode",
11 | "version" : 1
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.github/workflows/build-iOS.yml:
--------------------------------------------------------------------------------
1 | name: Build for iOS
2 | on:
3 | pull_request:
4 |
5 | jobs:
6 | build_iOS:
7 | runs-on: macos-latest
8 | steps:
9 | - uses: actions/checkout@v3
10 |
11 | - name: Swift build (iOS)
12 | run: xcrun xcodebuild clean build -quiet -scheme DynamicUI -destination generic/platform=iOS
13 |
--------------------------------------------------------------------------------
/.github/workflows/build-tvOS.yml:
--------------------------------------------------------------------------------
1 | name: Build for tvOS
2 | on:
3 | pull_request:
4 |
5 | jobs:
6 | build_tvOS:
7 | runs-on: macos-latest
8 | steps:
9 | - uses: actions/checkout@v3
10 |
11 | - name: Swift build (tvOS)
12 | run: xcrun xcodebuild clean build -quiet -scheme DynamicUI -destination generic/platform=tvOS
13 |
--------------------------------------------------------------------------------
/.github/workflows/build-visionOS.yml:
--------------------------------------------------------------------------------
1 | name: Build for visionOS
2 | on:
3 | pull_request:
4 |
5 | jobs:
6 | build_visionOS:
7 | runs-on: macos-latest
8 | steps:
9 | - uses: actions/checkout@v3
10 |
11 | - name: Swift build (visionOS)
12 | run: xcrun xcodebuild clean build -quiet -scheme DynamicUI -destination generic/platform=xrOS
13 |
--------------------------------------------------------------------------------
/.github/workflows/build-watchOS.yml:
--------------------------------------------------------------------------------
1 | name: Build for watchOS
2 | on:
3 | pull_request:
4 |
5 | jobs:
6 | build_watchOS:
7 | runs-on: macos-latest
8 | steps:
9 | - uses: actions/checkout@v3
10 |
11 | - name: Swift build (watchOS)
12 | run: xcrun xcodebuild clean build -quiet -scheme DynamicUI -destination generic/platform=watchOS
13 |
--------------------------------------------------------------------------------
/Playground/DynamicUI Watch App/DynamicUIApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicUIApp.swift
3 | // DynamicUI Watch App
4 | //
5 | // Created by Wesley de Groot on 06/10/2025.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct DynamicUIWatchAppApp: App {
12 | var body: some Scene {
13 | WindowGroup {
14 | ContentView()
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Playground/DynamicUI Playground/DynamicUI_PlaygroundApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicUI_PlaygroundApp.swift
3 | // DynamicUI Playground
4 | //
5 | // Created by Wesley de Groot on 18/07/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct DynamicUIPlaygroundApp: App {
12 | var body: some Scene {
13 | WindowGroup {
14 | ContentView()
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Playground/DynamicUI Playground/DynamicUI_Playground.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | excluded:
2 | - "*resource_bundle_accessor*" # SwiftPM Generated
3 | - ".build/*"
4 |
5 | disabled_rules:
6 | - todo
7 |
8 | opt_in_rules:
9 | - missing_docs
10 | - empty_count
11 | - empty_string
12 | - toggle_bool
13 | - unused_optional_binding
14 | - valid_ibinspectable
15 | - modifier_order
16 | - first_where
17 | - fatal_error_message
18 | - force_unwrapping
19 |
20 |
--------------------------------------------------------------------------------
/Tests/DynamicUITests/DynamicUITests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import DynamicUI
3 |
4 | final class DynamicUITests: XCTestCase {
5 | func testExample() throws {
6 | // XCTest Documentation
7 | // https://developer.apple.com/documentation/xctest
8 |
9 | // Defining Test Cases and Test Methods
10 | // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/.github/workflows/build-documentation.yml:
--------------------------------------------------------------------------------
1 | # .github/workflows/build-documentation.yml
2 |
3 | name: build-documentation
4 |
5 | on:
6 | # Run on push to main branch
7 | push:
8 | branches:
9 | - main
10 |
11 | # Dispatch if triggered using Github (website)
12 | workflow_dispatch:
13 |
14 | jobs:
15 | Build-documentation:
16 | runs-on: macos-latest
17 | steps:
18 | - name: Build documentation
19 | uses: 0xWDG/build-documentation@main
20 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUI.docc/Views/Divider.md:
--------------------------------------------------------------------------------
1 | # Divider Example
2 |
3 | This example demonstrates how to define a `Divider` using DynamicUI's JSON schema.
4 |
5 | ```json
6 | [
7 | {
8 | "type": "Divider"
9 | }
10 | ]
11 | ```
12 |
13 | ### Parameters
14 |
15 | | Parameter | Type | Description |
16 | | --------- | ----------- | --------------------------------- |
17 | | title | String | The title of the divider. |
18 | | modifiers | Object | The visual modifiers for the divider. |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUI.docc/Views/Text.md:
--------------------------------------------------------------------------------
1 | # Text Example
2 |
3 | This example demonstrates how to define a `Text` using DynamicUI's JSON schema.
4 |
5 | ```json
6 | [
7 | {
8 | "type": "Text",
9 | "title": "Enable Feature"
10 | }
11 | ]
12 | ```
13 |
14 | ### Parameters
15 |
16 | | Parameter | Type | Description |
17 | | --------- | ----------- | --------------------------------- |
18 | | title | String | The title of the Text. |
19 | | modifiers | Object | The visual modifiers for the Text. |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUI.docc/Views/TextEditor.md:
--------------------------------------------------------------------------------
1 | # TextEditor Example
2 |
3 | This example demonstrates how to define a `TextEditor` using DynamicUI's JSON schema.
4 |
5 | ```json
6 | [
7 | {
8 | "type": "TextEditor",
9 | "defaultValue": "This is the default text."
10 | }
11 | ]
12 | ```
13 |
14 | ### Parameters
15 |
16 | | Parameter | Type | Description |
17 | | --------- | ----------- | --------------------------------- |
18 | | defaultValue | String | The default value of the text editor. |
19 | | modifiers | Object | The visual modifiers for the text editor. |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUI.docc/Views/Image.md:
--------------------------------------------------------------------------------
1 | # Image Example
2 |
3 | This example demonstrates how to define a `Image` using DynamicUI's JSON schema.
4 |
5 | ```json
6 | [
7 | {
8 | "type": "Image",
9 | "url": "star.fill"
10 | }
11 | ]
12 | ```
13 |
14 | ### Parameters
15 |
16 | | Parameter | Type | Description |
17 | | --------- | ----------- | --------------------------------- |
18 | | title | String | The title of the image. (accessibility) |
19 | | url | String | The URL of the image. |
20 | | modifiers | Object | The visual modifiers for the image. |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUI.docc/Views/Button.md:
--------------------------------------------------------------------------------
1 | # Button Example
2 |
3 | This example demonstrates how to define a `Button` using DynamicUI's JSON schema.
4 |
5 | ```json
6 | [
7 | {
8 | "type": "Button",
9 | "title": "Title",
10 | "modifiers": {
11 | "foregroundColor": "purple"
12 | }
13 | }
14 | ]
15 | ```
16 |
17 | ### Parameters
18 |
19 | | Parameter | Type | Description |
20 | | --------- | ----------- | --------------------------------- |
21 | | title | String | The title of the button. |
22 | | modifiers | Object | The visual modifiers for the button. |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUI.docc/Views/Gauge.md:
--------------------------------------------------------------------------------
1 | # Gauge Example
2 |
3 | This example demonstrates how to define a `Gauge` using DynamicUI's JSON schema.
4 |
5 | ```json
6 | [
7 | {
8 | "type": "Gauge",
9 | "title": "Gauge",
10 | "defaultValue": 0.5
11 | }
12 | ]
13 | ```
14 |
15 | ### Parameters
16 |
17 | | Parameter | Type | Description |
18 | | --------- | ----------- | --------------------------------- |
19 | | title | String | The title of the gauge. |
20 | | defaultValue | Number | The default value of the gauge. |
21 | | modifiers | Object | The visual modifiers for the gauge. |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUI.docc/Views/Label.md:
--------------------------------------------------------------------------------
1 | # Label Example
2 |
3 | This example demonstrates how to define a `Label` using DynamicUI's JSON schema.
4 |
5 | ```json
6 | [
7 | {
8 | "type": "Label",
9 | "title": "Label text",
10 | "url": "star"
11 | }
12 | ]
13 | ```
14 |
15 | ### Parameters
16 |
17 | | Parameter | Type | Description |
18 | | --------- | ----------- | --------------------------------- |
19 | | title | String | The title of the Label. |
20 | | url | String | The SF Symbol name for the Label's icon. |
21 | | modifiers | Object | The visual modifiers for the Label. |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUI.docc/Views/Toggle.md:
--------------------------------------------------------------------------------
1 | # Toggle Example
2 |
3 | This example demonstrates how to define a `Toggle` using DynamicUI's JSON schema.
4 |
5 | ```json
6 | [
7 | {
8 | "type": "Toggle",
9 | "title": "Enable Feature",
10 | "defaultValue": true
11 | }
12 | ]
13 | ```
14 |
15 | ### Parameters
16 |
17 | | Parameter | Type | Description |
18 | | --------- | ----------- | --------------------------------- |
19 | | title | String | The title of the Toggle. |
20 | | defaultValue | Boolean | The state of the Toggle (true for on, false for off). |
21 | | modifiers | Object | The visual modifiers for the Toggle. |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUI.docc/Views/List.md:
--------------------------------------------------------------------------------
1 | # List Example
2 |
3 | This example demonstrates how to define a `List` using DynamicUI's JSON schema.
4 |
5 | ```json
6 | [
7 | {
8 | "type": "List",
9 | "children": [
10 | {
11 | "type": "Text",
12 | "title": "Content goes here",
13 | }
14 | ]
15 | }
16 | }
17 | ]
18 | ```
19 |
20 | ### Parameters
21 |
22 | | Parameter | Type | Description |
23 | | --------- | ----------- | --------------------------------- |
24 | | children | Array | The child elements of the List. |
25 | | modifiers | Object | The visual modifiers for the List. |
26 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUI.docc/Views/HStack.md:
--------------------------------------------------------------------------------
1 | # HStack Example
2 |
3 | This example demonstrates how to define a `HStack` using DynamicUI's JSON schema.
4 |
5 | ```json
6 | [
7 | {
8 | "type": "HStack",
9 | "children": [
10 | {
11 | "type": "Text",
12 | "title": "Content goes here",
13 | }
14 | ]
15 | }
16 | }
17 | ]
18 | ```
19 |
20 | ### Parameters
21 |
22 | | Parameter | Type | Description |
23 | | --------- | ----------- | --------------------------------- |
24 | | children | Array | The child elements of the HStack. |
25 | | modifiers | Object | The visual modifiers for the HStack. |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUI.docc/Views/VStack.md:
--------------------------------------------------------------------------------
1 | # VStack Example
2 |
3 | This example demonstrates how to define a `VStack` using DynamicUI's JSON schema.
4 |
5 | ```json
6 | [
7 | {
8 | "type": "VStack",
9 | "children": [
10 | {
11 | "type": "Text",
12 | "title": "Content goes here",
13 | }
14 | ]
15 | }
16 | }
17 | ]
18 | ```
19 |
20 | ### Parameters
21 |
22 | | Parameter | Type | Description |
23 | | --------- | ----------- | --------------------------------- |
24 | | children | Array | The child elements of the VStack. |
25 | | modifiers | Object | The visual modifiers for the VStack. |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUI.docc/Views/ZStack.md:
--------------------------------------------------------------------------------
1 | # ZStack Example
2 |
3 | This example demonstrates how to define a `ZStack` using DynamicUI's JSON schema.
4 |
5 | ```json
6 | [
7 | {
8 | "type": "ZStack",
9 | "children": [
10 | {
11 | "type": "Text",
12 | "title": "Content goes here",
13 | }
14 | ]
15 | }
16 | }
17 | ]
18 | ```
19 |
20 | ### Parameters
21 |
22 | | Parameter | Type | Description |
23 | | --------- | ----------- | --------------------------------- |
24 | | children | Array | The child elements of the ZStack. |
25 | | modifiers | Object | The visual modifiers for the ZStack. |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUI.docc/Views/HSplitView.md:
--------------------------------------------------------------------------------
1 | # HSplitView Example
2 |
3 | This example demonstrates how to define a `HSplitView` using DynamicUI's JSON schema.
4 |
5 | ```json
6 | [
7 | {
8 | "type": "HSplitView",
9 | "children": [
10 | {
11 | "type": "Text",
12 | "title": "Content goes here",
13 | }
14 | ]
15 | }
16 | }
17 | ]
18 | ```
19 |
20 | ### Parameters
21 |
22 | | Parameter | Type | Description |
23 | | --------- | ----------- | --------------------------------- |
24 | | children | Array | The child elements of the HSplitView. |
25 | | modifiers | Object | The visual modifiers for the HSplitView. |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUI.docc/Views/ScrollView.md:
--------------------------------------------------------------------------------
1 | # ScrollView Example
2 |
3 | This example demonstrates how to define a `ScrollView` using DynamicUI's JSON schema.
4 |
5 | ```json
6 | [
7 | {
8 | "type": "ScrollView",
9 | "children": [
10 | {
11 | "type": "Text",
12 | "title": "Content goes here",
13 | }
14 | ]
15 | }
16 | }
17 | ]
18 | ```
19 |
20 | ### Parameters
21 |
22 | | Parameter | Type | Description |
23 | | --------- | ----------- | --------------------------------- |
24 | | children | Array | The child elements of the ScrollView. |
25 | | modifiers | Object | The visual modifiers for the ScrollView. |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUI.docc/Views/TextField.md:
--------------------------------------------------------------------------------
1 | # TextField Example
2 |
3 | This example demonstrates how to define a `TextField` using DynamicUI's JSON schema.
4 |
5 | ```json
6 | [
7 | {
8 | "type": "TextField",
9 | "title": "Title",
10 | "defaultValue": "This is the default text."
11 | }
12 | ]
13 | ```
14 |
15 | ### Parameters
16 |
17 | | Parameter | Type | Description |
18 | | ------------ | ----------- | --------------------------------------- |
19 | | title | String | The title(label) of the TextField. |
20 | | defaultValue | String | The default value of the text field. |
21 | | modifiers | Object | The visual modifiers for the TextField. |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUI.docc/Views/VSplitView.md:
--------------------------------------------------------------------------------
1 | # VSplitView Example
2 |
3 | This example demonstrates how to define a `VSplitView` using DynamicUI's JSON schema.
4 |
5 | ```json
6 | [
7 | {
8 | "type": "VSplitView",
9 | "children": [
10 | {
11 | "type": "Text",
12 | "title": "Content goes here",
13 | }
14 | ]
15 | }
16 | }
17 | ]
18 | ```
19 |
20 | ### Parameters
21 |
22 | | Parameter | Type | Description |
23 | | --------- | ----------- | --------------------------------- |
24 | | children | Array | The child elements of the VSplitView. |
25 | | modifiers | Object | The visual modifiers for the VSplitView. |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUI.docc/Views/NavigationView.md:
--------------------------------------------------------------------------------
1 | # NavigationView Example
2 |
3 | This example demonstrates how to define a `NavigationView` using DynamicUI's JSON schema.
4 |
5 | ```json
6 | [
7 | {
8 | "type": "NavigationView",
9 | "children": [
10 | {
11 | "type": "Text",
12 | "title": "Content goes here",
13 | }
14 | ]
15 | }
16 | }
17 | ]
18 | ```
19 |
20 | ### Parameters
21 |
22 | | Parameter | Type | Description |
23 | | --------- | ----------- | --------------------------------- |
24 | | children | Array | The child elements of the NavigationView. |
25 | | modifiers | Object | The visual modifiers for the NavigationView. |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUI.docc/Views/Group.md:
--------------------------------------------------------------------------------
1 | # Group Example
2 |
3 | This example demonstrates how to define a `Group` using DynamicUI's JSON schema.
4 |
5 | ```json
6 | [
7 | {
8 | "type": "Group",
9 | "children": [
10 | {
11 | "type": "Text",
12 | "title": "Content goes here",
13 | }
14 | ]
15 | }
16 | }
17 | ]
18 | ```
19 |
20 | ### Parameters
21 |
22 | | Parameter | Type | Description |
23 | | --------- | ----------- | --------------------------------- |
24 | | title | String | The title of the group. |
25 | | children | Array | The child elements of the group. |
26 | | modifiers | Object | The visual modifiers for the group. |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Extensions/Binding.onChange.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Binding.onChange.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 16/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | extension Binding {
14 | /// On Change of a Binding value
15 | ///
16 | /// - Parameter handler: Callback handler
17 | ///
18 | /// - Returns: Binding
19 | func onChange(_ callback: @escaping (Value) -> Void) -> Binding {
20 | Binding(
21 | get: { self.wrappedValue },
22 | set: { newValue in
23 | self.wrappedValue = newValue
24 | callback(newValue)
25 | }
26 | )
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUI.docc/Views/GroupBox.md:
--------------------------------------------------------------------------------
1 | # GroupBox Example
2 |
3 | This example demonstrates how to define a `GroupBox` using DynamicUI's JSON schema.
4 |
5 | ```json
6 | [
7 | {
8 | "type": "GroupBox",
9 | "children": [
10 | {
11 | "type": "Text",
12 | "title": "Content goes here",
13 | }
14 | ]
15 | }
16 | }
17 | ]
18 | ```
19 |
20 | ### Parameters
21 |
22 | | Parameter | Type | Description |
23 | | --------- | ----------- | --------------------------------- |
24 | | title | String | The title of the groupbox. |
25 | | children | Array | The child elements of the groupbox. |
26 | | modifiers | Object | The visual modifiers for the groupbox. |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUI.docc/Views/SecureTextField.md:
--------------------------------------------------------------------------------
1 | # SecureTextField Example
2 |
3 | This example demonstrates how to define a `SecureTextField` using DynamicUI's JSON schema.
4 |
5 | ```json
6 | [
7 | {
8 | "type": "SecureTextField",
9 | "title": "Title",
10 | "defaultValue": "This is the default text."
11 | }
12 | ]
13 | ```
14 |
15 | ### Parameters
16 |
17 | | Parameter | Type | Description |
18 | | ------------ | ----------- | --------------------------------------------- |
19 | | title | String | The title(label) of the SecureTextField. |
20 | | defaultValue | String | The default value of the secure text field. |
21 | | modifiers | Object | The visual modifiers for the SecureTextField. |
22 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUI.docc/Views/Section.md:
--------------------------------------------------------------------------------
1 | # Section Example
2 |
3 | This example demonstrates how to define a `Section` using DynamicUI's JSON schema.
4 |
5 | ```json
6 | [
7 | {
8 | "type": "Section",
9 | "title": "Section Title",
10 | "children": [
11 | {
12 | "type": "Text",
13 | "title": "Content goes here",
14 | }
15 | ]
16 | }
17 | }
18 | ]
19 | ```
20 |
21 | ### Parameters
22 |
23 | | Parameter | Type | Description |
24 | | --------- | ----------- | --------------------------------- |
25 | | title | String | The title of the Section. |
26 | | children | Array | The child elements of the Section. |
27 | | modifiers | Object | The visual modifiers for the Section. |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUI.docc/Views/DisclosureGroup.md:
--------------------------------------------------------------------------------
1 | # DisclosureGroup Example
2 |
3 | This example demonstrates how to define a `DisclosureGroup` using DynamicUI's JSON schema.
4 |
5 | ```json
6 | [
7 | {
8 | "type": "DisclosureGroup",
9 | "title": "Title",
10 | "children": [
11 | {
12 | "type": "Text",
13 | "title": "Content goes here",
14 | }
15 | ]
16 | }
17 | }
18 | ]
19 | ```
20 |
21 | ### Parameters
22 |
23 | | Parameter | Type | Description |
24 | | --------- | ----------- | --------------------------------- |
25 | | title | String | The title of the disclosure group.|
26 | | children | Array | The child elements of the disclosure group. |
27 | | modifiers | Object | The visual modifiers for the disclosure group. |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUI.docc/Views/ProgressView.md:
--------------------------------------------------------------------------------
1 | # ProgressView Example
2 |
3 | This example demonstrates how to define a `ProgressView` using DynamicUI's JSON schema.
4 |
5 | ```json
6 | [
7 | {
8 | "type": "ProgressView",
9 | "title": "Title",
10 | "defaultValue": 0.5,
11 | "maximumValue": 1.0,
12 | "modifiers": {
13 | "foregroundColor": "purple"
14 | }
15 | }
16 | ]
17 | ```
18 |
19 | ### Parameters
20 |
21 | | Parameter | Type | Description |
22 | | --------- | ----------- | --------------------------------- |
23 | | title | String | The title of the ProgressView. |
24 | | defaultValue | Double | The default value of the progress. |
25 | | maximumValue | Double | The maximum value of the progress. |
26 | | modifiers | Object | The visual modifiers for the ProgressView. |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.8.0
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "DynamicUI",
8 | platforms: [
9 | .iOS(.v15),
10 | .macOS(.v12),
11 | .tvOS(.v14),
12 | .watchOS(.v7),
13 | .macCatalyst(.v15)
14 | ],
15 | products: [
16 | // Products define the executables and libraries a package produces, making them visible to other packages.
17 | .library(
18 | name: "DynamicUI",
19 | targets: ["DynamicUI"]
20 | )
21 | ],
22 | targets: [
23 | // Targets are the basic building blocks of a package, defining a module or a test suite.
24 | // Targets can depend on other targets in this package and products from dependencies.
25 | .target(
26 | name: "DynamicUI"
27 | ),
28 | .testTarget(
29 | name: "DynamicUITests",
30 | dependencies: ["DynamicUI"]
31 | )
32 | ]
33 | )
34 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUI.docc/Views/Picker.md:
--------------------------------------------------------------------------------
1 | # Picker Example
2 |
3 | This example demonstrates how to define a `Picker` using DynamicUI's JSON schema.
4 |
5 | ```json
6 | [
7 | {
8 | "type": "Picker",
9 | "children": [
10 | {
11 | "identifier": "item1",
12 | "type": "Text",
13 | "title": "Item 1"
14 | },
15 | {
16 | "identifier": "item2",
17 | "type": "Text",
18 | "title": "Item 2",
19 | "disabled": true
20 | },
21 | {
22 | "identifier": "item3",
23 | "type": "Text",
24 | "title": "Item 3"
25 | }
26 | ]
27 | }
28 | }
29 | ]
30 | ```
31 |
32 | ### Parameters
33 |
34 | | Parameter | Type | Description |
35 | | --------- | ----------- | --------------------------------- |
36 | | children | Array | The child elements of the Picker. |
37 | | modifiers | Object | The visual modifiers for the Picker. |
--------------------------------------------------------------------------------
/LICENCE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Wesley de Groot, email+OSS@WesleydeGroot.nl
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Playground/DynamicUI Playground/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "scale" : "1x",
6 | "size" : "16x16"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "2x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "1x",
16 | "size" : "32x32"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "2x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "1x",
26 | "size" : "128x128"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "2x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "1x",
36 | "size" : "256x256"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "2x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "1x",
46 | "size" : "512x512"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "2x",
51 | "size" : "512x512"
52 | }
53 | ],
54 | "info" : {
55 | "author" : "xcode",
56 | "version" : 1
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicVStack.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicVStack.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 19/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: VStack
14 | ///
15 | /// DynamicVStack is a SwiftUI View that can be used to display an VStack.
16 | ///
17 | /// JSON Example:
18 | /// ```json
19 | /// {
20 | /// "type": "VStack",
21 | /// "children": [ ]
22 | /// }
23 | /// ```
24 | ///
25 | /// - Note: This is a internal view, you should not use this directly. \
26 | /// Use ``DynamicUI`` instead.
27 | struct DynamicVStack: View {
28 | @Environment(\.internalDynamicUIEnvironment)
29 | /// Internal: dynamicUIEnvironment
30 | private var dynamicUIEnvironment
31 |
32 | /// The component to display
33 | private let component: DynamicUIComponent
34 |
35 | /// Initialize the DynamicVStack
36 | init(_ component: DynamicUIComponent) {
37 | self.component = component
38 | }
39 |
40 | /// Generated body for SwiftUI
41 | var body: some View {
42 | VStack {
43 | if let children = component.children {
44 | AnyView(dynamicUIEnvironment.buildView(for: children))
45 | }
46 | }
47 | .set(modifiers: component)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicZStack.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicZStack.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 19/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: ZStack
14 | ///
15 | /// DynamicZStack is a SwiftUI View that can be used to display an ZStack.
16 | ///
17 | /// JSON Example:
18 | /// ```json
19 | /// {
20 | /// "type": "ZStack",
21 | /// "children": [ ]
22 | /// }
23 | /// ```
24 | ///
25 | /// - Note: This is a internal view, you should not use this directly. \
26 | /// Use ``DynamicUI`` instead.
27 | struct DynamicZStack: View {
28 | @Environment(\.internalDynamicUIEnvironment)
29 | /// Internal: dynamicUIEnvironment
30 | private var dynamicUIEnvironment
31 |
32 | /// The component to display
33 | private let component: DynamicUIComponent
34 |
35 | /// Initialize the DynamicZStack
36 | init(_ component: DynamicUIComponent) {
37 | self.component = component
38 | }
39 |
40 | /// Generated body for SwiftUI
41 | var body: some View {
42 | ZStack {
43 | if let children = component.children {
44 | AnyView(dynamicUIEnvironment.buildView(for: children))
45 | }
46 | }
47 | .set(modifiers: component)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicTEMPLATE.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicTEMPLATE.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 19/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: TEMPLATE
14 | ///
15 | /// DynamicTEMPLATE is a SwiftUI View that can be used to display an TEMPLATE.
16 | ///
17 | /// JSON Example:
18 | /// ```json
19 | /// {
20 | /// "type": "TEMPLATE",
21 | /// "title": "Title"
22 | /// }
23 | /// ```
24 | ///
25 | /// - Note: This is a internal view, you should not use this directly. \
26 | /// Use ``DynamicUI`` instead.
27 | struct DynamicTEMPLATE: View {
28 | @Environment(\.internalDynamicUIEnvironment)
29 | /// Internal: dynamicUIEnvironment
30 | private var dynamicUIEnvironment
31 |
32 | @State
33 | /// The state of the TEMPLATE
34 | private var state: Double
35 |
36 | /// The component to display
37 | private let component: DynamicUIComponent
38 |
39 | /// Initialize the DynamicTEMPLATE
40 | init(_ component: DynamicUIComponent) {
41 | self.state = component.defaultValue?.toDouble() ?? 0
42 | self.component = component
43 | }
44 |
45 | /// Generated body for SwiftUI
46 | var body: some View {
47 | EmptyView()
48 | .set(modifiers: component)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## User settings
2 | xcuserdata/
3 |
4 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
5 | *.xcscmblueprint
6 | *.xccheckout
7 |
8 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
9 | build/
10 | DerivedData/
11 | *.moved-aside
12 | *.pbxuser
13 | !default.pbxuser
14 | *.mode1v3
15 | !default.mode1v3
16 | *.mode2v3
17 | !default.mode2v3
18 | *.perspectivev3
19 | !default.perspectivev3
20 |
21 | ## Obj-C/Swift specific
22 | *.hmap
23 |
24 | ## App packaging
25 | *.ipa
26 | *.dSYM.zip
27 | *.dSYM
28 |
29 | ## Playgrounds
30 | timeline.xctimeline
31 | playground.xcworkspace
32 |
33 | ### Swift Package Manager
34 | Packages/
35 | Package.pins
36 | Package.resolved
37 | # *.xcodeproj
38 | #
39 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
40 | # hence it is not needed unless you have added a package configuration file to your project
41 | .swiftpm
42 | .build/
43 |
44 | ### CocoaPods
45 | Pods/
46 | *.xcworkspace
47 |
48 | ### Carthage
49 | Carthage/Checkouts
50 | Carthage/Build/
51 |
52 | ### Accio dependency management
53 | Dependencies/
54 | .accio/
55 |
56 | ### fastlane
57 | fastlane/report.xml
58 | fastlane/Preview.html
59 | fastlane/screenshots/**/*.png
60 | fastlane/test_output
61 |
62 | ### Code Injection
63 | iOSInjectionProject/
64 |
65 | ### Generated docs
66 | .build/
67 | docs/
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicScrollView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicScrollView.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 19/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: ScrollView
14 | ///
15 | /// DynamicScrollView is a SwiftUI View that can be used to display an ScrollView.
16 | ///
17 | /// JSON Example:
18 | /// ```json
19 | /// {
20 | /// "type": "ScrollView",
21 | /// "children": [ ]
22 | /// }
23 | /// ```
24 | ///
25 | /// - Note: This is a internal view, you should not use this directly. \
26 | /// Use ``DynamicUI`` instead.
27 | struct DynamicScrollView: View {
28 | @Environment(\.internalDynamicUIEnvironment)
29 | /// Internal: dynamicUIEnvironment
30 | private var dynamicUIEnvironment
31 |
32 | /// The component to display
33 | private let component: DynamicUIComponent
34 |
35 | /// Initialize the DynamicScrollView
36 | init(_ component: DynamicUIComponent) {
37 | self.component = component
38 | }
39 |
40 | /// Generated body for SwiftUI
41 | var body: some View {
42 | ScrollView {
43 | if let children = component.children {
44 | AnyView(dynamicUIEnvironment.buildView(for: children))
45 | }
46 | }
47 | .set(modifiers: component)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUI.docc/Views/Form.md:
--------------------------------------------------------------------------------
1 | # Form Example
2 |
3 | This example demonstrates how to define a `Form` using DynamicUI's JSON schema.
4 | The form includes a section with three toggles, each illustrating different configuration options.
5 |
6 | ```json
7 | [
8 | {
9 | "type": "Form",
10 | "children": [
11 | {
12 | "type": "Section",
13 | "title": "Form example",
14 | "children": [
15 | {
16 | "type": "Toggle",
17 | "title": "Toggle"
18 | },
19 | {
20 | "type": "Toggle",
21 | "title": "Toggle",
22 | "defaultValue": true
23 | },
24 | {
25 | "type": "Toggle",
26 | "title": "Toggle",
27 | "defaultValue": true,
28 | "disabled": true
29 | }
30 | ]
31 | }
32 | ]
33 | }
34 | ]
35 | ```
36 |
37 | ### Parameters
38 |
39 | | Parameter | Type | Description |
40 | | --------- | ----------- | --------------------------------- |
41 | | children | Array | The child elements of the form. |
42 | | modifiers | Object | The visual modifiers for the form. |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicNavigationView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicNavigationView.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 19/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: NavigationView
14 | ///
15 | /// DynamicNavigationView is a SwiftUI View that can be used to display an NavigationView.
16 | ///
17 | /// JSON Example:
18 | /// ```json
19 | /// {
20 | /// "type": "Label",
21 | /// "title": "Title"
22 | /// }
23 | /// ```
24 | ///
25 | /// - Note: This is a internal view, you should not use this directly. \
26 | /// Use ``DynamicUI`` instead.
27 | struct DynamicNavigationView: View {
28 | @Environment(\.internalDynamicUIEnvironment)
29 | /// Internal: dynamicUIEnvironment
30 | private var dynamicUIEnvironment
31 |
32 | /// The component to display
33 | private let component: DynamicUIComponent
34 |
35 | /// Initialize the DynamicNavigationView
36 | init(_ component: DynamicUIComponent) {
37 | self.component = component
38 | }
39 |
40 | /// Generated body for SwiftUI
41 | var body: some View {
42 | NavigationView {
43 | if let children = component.children {
44 | AnyView(dynamicUIEnvironment.buildView(for: children))
45 | }
46 | }
47 | .set(modifiers: component)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicLabel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicLabel.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 19/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: Label
14 | ///
15 | /// DynamicLabel is a SwiftUI View that can be used to display an Label.
16 | ///
17 | /// JSON Example:
18 | /// ```json
19 | /// {
20 | /// "type": "Label",
21 | /// "title": "Title"
22 | /// }
23 | /// ```
24 | ///
25 | /// - Note: This is a internal view, you should not use this directly. \
26 | /// Use ``DynamicUI`` instead.
27 | struct DynamicLabel: View {
28 | @Environment(\.internalDynamicUIEnvironment)
29 | /// Internal: dynamicUIEnvironment
30 | private var dynamicUIEnvironment
31 |
32 | /// The component to display
33 | private let component: DynamicUIComponent
34 |
35 | /// Initialize the DynamicLabel
36 | init(_ component: DynamicUIComponent) {
37 | self.component = component
38 | }
39 |
40 | /// Generated body for SwiftUI
41 | var body: some View {
42 | if let systemImage = component.url {
43 | Label(
44 | component.title ?? "Label",
45 | systemImage: systemImage
46 | )
47 | .set(modifiers: component)
48 | } else {
49 | DynamicText(component)
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicVSplitView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicVSplitView.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 19/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: VSplitView
14 | ///
15 | /// DynamicVSplitView is a SwiftUI View that can be used to display an VSplitView.
16 | ///
17 | /// JSON Example:
18 | /// ```json
19 | /// {
20 | /// "type": "VSplitView",
21 | /// "children": [ ]
22 | /// }
23 | /// ```
24 | ///
25 | /// - Note: This is a internal view, you should not use this directly. \
26 | /// Use ``DynamicUI`` instead.
27 | struct DynamicVSplitView: View {
28 | @Environment(\.internalDynamicUIEnvironment)
29 | /// Internal: dynamicUIEnvironment
30 | private var dynamicUIEnvironment
31 |
32 | /// The component to display
33 | private let component: DynamicUIComponent
34 |
35 | /// Initialize the DynamicVSplitView
36 | init(_ component: DynamicUIComponent) {
37 | self.component = component
38 | }
39 |
40 | /// Generated body for SwiftUI
41 | var body: some View {
42 | #if os(macOS)
43 | VSplitView {
44 | if let children = component.children {
45 | AnyView(dynamicUIEnvironment.buildView(for: children))
46 | }
47 | }
48 | .set(modifiers: component)
49 | #else
50 | EmptyView()
51 | #endif
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicImage.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 19/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: Image
14 | ///
15 | /// DynamicImage is a SwiftUI View that can be used to display an image.
16 | ///
17 | /// JSON Example:
18 | /// ```json
19 | /// {
20 | /// "type": "Image",
21 | /// "url": "systemName"
22 | /// }
23 | /// ```
24 | ///
25 | /// - Note: The `url` is the systemName of the image.
26 | /// You can use any systemName from SF Symbols.
27 | /// For more information, see https://developer.apple.com/sf-symbols/
28 | ///
29 | /// - Note: This is a internal view, you should not use this directly. \
30 | /// Use ``DynamicUI`` instead.
31 | struct DynamicImage: View {
32 | @Environment(\.internalDynamicUIEnvironment)
33 | /// Internal: dynamicUIEnvironment
34 | private var dynamicUIEnvironment
35 |
36 | /// The component to display
37 | private let component: DynamicUIComponent
38 |
39 | /// Initialize the DynamicImage
40 | init(_ component: DynamicUIComponent) {
41 | self.component = component
42 | }
43 |
44 | /// Generated body for SwiftUI
45 | var body: some View {
46 | Image(systemName: component.url ?? "")
47 | .accessibilityLabel(component.title ?? "")
48 | .set(modifiers: component)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicSecureTextField.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicSecureField.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 19/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: SecureField
14 | ///
15 | /// DynamicSecureField is a SwiftUI View that can be used to display an SecureField.
16 | ///
17 | /// JSON Example:
18 | /// ```json
19 | /// {
20 | /// "type": "SecureField",
21 | /// "title": "Title",
22 | /// "defaultValue": "Default Value"
23 | /// }
24 | /// ```
25 | ///
26 | /// - Note: This is a internal view, you should not use this directly. \
27 | /// Use ``DynamicUI`` instead.
28 | struct DynamicSecureField: View {
29 | @Environment(\.internalDynamicUIEnvironment)
30 | /// Internal: dynamicUIEnvironment
31 | var dynamicUIEnvironment
32 |
33 | @State
34 | /// The state of the SecureField
35 | private var state: String
36 |
37 | /// The component to display
38 | private let component: DynamicUIComponent
39 |
40 | /// Initialize the DynamicSecureField
41 | init(_ component: DynamicUIComponent) {
42 | self.state = component.defaultValue?.toString() ?? ""
43 | self.component = component
44 | }
45 |
46 | /// Generated body for SwiftUI
47 | var body: some View {
48 | SecureField(
49 | "\(component.title ?? "")",
50 | text: $state
51 | )
52 | .set(modifiers: component)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicTextField.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicTextField.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 19/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: TextField
14 | ///
15 | /// DynamicTextField is a SwiftUI View that can be used to display an TextField.
16 | ///
17 | /// JSON Example:
18 | /// ```json
19 | /// {
20 | /// "type": "TextField",
21 | /// "title": "Title",
22 | /// "defaultValue": "Default Value"
23 | /// }
24 | /// ```
25 | ///
26 | /// - Note: This is a internal view, you should not use this directly. \
27 | /// Use ``DynamicUI`` instead.
28 | struct DynamicTextField: View {
29 | @Environment(\.internalDynamicUIEnvironment)
30 | /// Internal: dynamicUIEnvironment
31 | var dynamicUIEnvironment
32 |
33 | @State
34 | /// The state of the TextField
35 | private var state: String
36 |
37 | /// The component to display
38 | private let component: DynamicUIComponent
39 |
40 | /// Initialize the DynamicTextField
41 | init(_ component: DynamicUIComponent) {
42 | self.state = component.defaultValue?.toString() ?? ""
43 | self.component = component
44 | }
45 |
46 | /// Generated body for SwiftUI
47 | var body: some View {
48 | TextField(
49 | "\(component.title ?? "")",
50 | text: $state.onChange({ _ in
51 | var newComponent = component
52 | newComponent.state = .string(state)
53 |
54 | dynamicUIEnvironment.component = newComponent
55 | })
56 | )
57 | .set(modifiers: component)
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicTextEditor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicTextEditor.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 19/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: TextEditor
14 | ///
15 | /// DynamicTextEditor is a SwiftUI View that can be used to display an TextEditor.
16 | ///
17 | /// JSON Example:
18 | /// ```json
19 | /// {
20 | /// "type": "TextEditor",
21 | /// "title": "Title",
22 | /// "defaultValue": "Default Value"
23 | /// }
24 | /// ```
25 | ///
26 | /// - Note: This is a internal view, you should not use this directly. \
27 | /// Use ``DynamicUI`` instead.
28 | struct DynamicTextEditor: View {
29 | @Environment(\.internalDynamicUIEnvironment)
30 | /// Internal: dynamicUIEnvironment
31 | var dynamicUIEnvironment
32 |
33 | @State
34 | /// The state of the TextEditor
35 | private var state: String
36 |
37 | /// The component to display
38 | private let component: DynamicUIComponent
39 |
40 | /// Initialize the DynamicTextEditor
41 | init(_ component: DynamicUIComponent) {
42 | self.state = component.defaultValue?.toString() ?? ""
43 | self.component = component
44 | }
45 |
46 | /// Generated body for SwiftUI
47 | var body: some View {
48 | #if os(iOS) || os(macOS)
49 | TextEditor(text: $state.onChange({ _ in
50 | var newComponent = component
51 | newComponent.state = .string(state)
52 |
53 | dynamicUIEnvironment.component = newComponent
54 | }))
55 | .set(modifiers: component)
56 | #else
57 | DynamicTextField(component)
58 | #endif
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Playground/DynamicUI Watch App/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // DynamicUI Watch App
4 | //
5 | // Created by Wesley de Groot on 06/10/2025.
6 | //
7 |
8 | import SwiftUI
9 | import DynamicUI
10 |
11 | struct ContentView: View {
12 | @State
13 | var text: String = """
14 | [
15 | {
16 | "type": "VStack",
17 | "children": [
18 | {
19 | "type": "Button",
20 | "title": "Click me",
21 | "eventHandler": "customHandler"
22 | },
23 | {
24 | "type": "Toggle",
25 | "title": "Toggle me",
26 | "identifier": "my.toggle.1"
27 | },
28 | {
29 | "type": "Text",
30 | "title": "_Wait_, am i generating views from JSON?",
31 | "modifiers": {
32 | "foregroundStyle": "red",
33 | "opacity": 0.5
34 | },
35 | },
36 | {
37 | "type": "Label",
38 | "title": "Shine",
39 | "url": "star"
40 | }
41 | ]
42 | }
43 | ]
44 | """
45 |
46 | @State
47 | private var component: DynamicUIComponent?
48 |
49 | @State
50 | private var error: Error?
51 |
52 | @State
53 | private var log: String = ""
54 |
55 | var body: some View {
56 | ScrollView {
57 | DynamicUI(
58 | json: text,
59 | component: $component,
60 | error: $error
61 | )
62 | Text("\(String(describing: component))")
63 | }
64 | }
65 | }
66 |
67 | #Preview {
68 | ContentView()
69 | }
70 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicPicker.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicPicker.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 19/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: Picker
14 | ///
15 | /// DynamicPicker is a SwiftUI View that can be used to display an Picker.
16 | ///
17 | /// JSON Example:
18 | /// ```json
19 | /// {
20 | /// "type": "Picker",
21 | /// "title": "Title",
22 | /// "values": ["...", "...", ]
23 | /// }
24 | /// ```
25 | ///
26 | /// - Note: This is a internal view, you should not use this directly. \
27 | /// Use ``DynamicUI`` instead.
28 | struct DynamicPicker: View {
29 | @Environment(\.internalDynamicUIEnvironment)
30 | /// Internal: dynamicUIEnvironment
31 | private var dynamicUIEnvironment
32 |
33 | @State
34 | /// The state of the picker
35 | private var state: Double
36 |
37 | /// The component to display
38 | private let component: DynamicUIComponent
39 |
40 | /// Initialize the DynamicPicker
41 | init(_ component: DynamicUIComponent) {
42 | self.state = component.defaultValue?.toDouble() ?? 0
43 | self.component = component
44 | }
45 |
46 | /// Generated body for SwiftUI
47 | var body: some View {
48 | Picker(component.title ?? "", selection: $state.onChange({ newState in
49 | var newComponent = component
50 | newComponent.state = .double(newState)
51 |
52 | dynamicUIEnvironment.component = newComponent
53 | })) {
54 | if let children = component.children {
55 | AnyView(dynamicUIEnvironment.buildView(for: children))
56 | }
57 | }
58 | .set(modifiers: component)
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicSpacer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicSpacer.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 19/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: Spacer
14 | ///
15 | /// DynamicSpacer is a SwiftUI View that can be used to display an Spacer.
16 | ///
17 | /// JSON Example:
18 | /// ```json
19 | /// {
20 | /// "type": "Spacer"
21 | /// }
22 | /// ```
23 | ///
24 | /// - Note: This is a internal view, you should not use this directly. \
25 | /// Use ``DynamicUI`` instead.
26 | struct DynamicSpacer: View {
27 | @Environment(\.internalDynamicUIEnvironment)
28 | /// Internal: dynamicUIEnvironment
29 | private var dynamicUIEnvironment
30 |
31 | /// The component to display
32 | private let component: DynamicUIComponent
33 |
34 | /// Initialize the DynamicSpacer
35 | init(_ component: DynamicUIComponent) {
36 | self.component = component
37 | }
38 |
39 | /// Generated body for SwiftUI
40 | var body: some View {
41 | Spacer()
42 | .set(modifiers: component)
43 | }
44 | }
45 |
46 | #if DEBUG
47 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
48 | #Preview("Spacer") {
49 | let json = """
50 | [
51 | {
52 | "type": "HStack",
53 | "children": [
54 | {
55 | "type": "Text",
56 | "title": "Left"
57 | },
58 | {
59 | "type": "Spacer"
60 | },
61 | {
62 | "type": "Text",
63 | "title": "Right"
64 | }
65 | ],
66 | "modifiers": {
67 | "padding": true
68 | }
69 | }
70 | ]
71 | """
72 |
73 | DynamicUI(json: json, component: .constant(nil))
74 | }
75 | #endif
76 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicButton.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 19/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: Button
14 | ///
15 | /// DynamicButton is a SwiftUI View that can be used to display an Button.
16 | ///
17 | /// JSON Example:
18 | /// ```json
19 | /// {
20 | /// "type": "Button",
21 | /// "title": "Title",
22 | /// "modifiers": {
23 | /// "foregroundColor": "purple"
24 | /// }
25 | /// }
26 | /// ```
27 | ///
28 | /// - Note: This is a internal view, you should not use this directly. \
29 | /// Use ``DynamicUI`` instead.
30 | struct DynamicButton: View {
31 | @Environment(\.internalDynamicUIEnvironment)
32 | /// Internal: dynamicUIEnvironment
33 | private var dynamicUIEnvironment
34 |
35 | /// The state of the Button
36 | @State
37 | private var state: Double
38 |
39 | /// The component to display
40 | private let component: DynamicUIComponent
41 |
42 | /// Initialize the DynamicButton
43 | init(_ component: DynamicUIComponent) {
44 | self.state = component.defaultValue?.toDouble() ?? 0
45 | self.component = component
46 | }
47 |
48 | /// Generated body for SwiftUI
49 | var body: some View {
50 | Button(action: {
51 | dynamicUIEnvironment.component = component
52 | }, label: {
53 | Text(component.title ?? "Button")
54 | })
55 | .set(modifiers: component)
56 | }
57 | }
58 |
59 | #if DEBUG
60 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
61 | #Preview("Button") {
62 | let json = """
63 | [
64 | {
65 | "type": "Button",
66 | "title": "Title",
67 | "modifiers": {
68 | "foregroundColor": "purple"
69 | }
70 | }
71 | ]
72 | """
73 |
74 | DynamicUI(json: json, component: .constant(nil))
75 | }
76 | #endif
77 |
--------------------------------------------------------------------------------
/Playground/DynamicUI Playground/Storyboard.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicDivider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicDivider.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 19/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: Divider
14 | ///
15 | /// DynamicDivider is a SwiftUI View that can be used to display an Divider.
16 | ///
17 | /// JSON Example:
18 | /// ```json
19 | /// {
20 | /// "type": "Divider"
21 | /// }
22 | /// ```
23 | ///
24 | /// - Note: This is a internal view, you should not use this directly. \
25 | /// Use ``DynamicUI`` instead.
26 | struct DynamicDivider: View {
27 | @Environment(\.internalDynamicUIEnvironment)
28 | /// Internal: dynamicUIEnvironment
29 | private var dynamicUIEnvironment
30 |
31 | /// The component to display
32 | private let component: DynamicUIComponent
33 |
34 | /// Initialize the DynamicDivider
35 | init(_ component: DynamicUIComponent) {
36 | self.component = component
37 | }
38 |
39 | /// Generated body for SwiftUI
40 | var body: some View {
41 | Divider()
42 | .set(modifiers: component)
43 | }
44 | }
45 |
46 | #if DEBUG
47 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
48 | #Preview("Divider") {
49 | let json = """
50 | [
51 | {
52 | "type": "VStack",
53 | "children": [
54 | {
55 | "type": "Text",
56 | "title": "Divider",
57 | "modifiers": {
58 | "foregroundColor": "purple"
59 | }
60 | },
61 | {
62 | "type": "Divider",
63 | "modifiers": {
64 | "foregroundColor": "purple"
65 | }
66 | }
67 | ]
68 | }
69 | ]
70 | """
71 |
72 | DynamicUI(json: json, component: .constant(nil))
73 | }
74 | #endif
75 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicProgressView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicProgressView.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 19/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: ProgressView
14 | ///
15 | /// DynamicProgressView is a SwiftUI View that can be used to display an progress view.
16 | ///
17 | /// JSON Example:
18 | /// ```json
19 | /// {
20 | /// "type": "ProgressView",
21 | /// "title": "Title",
22 | /// "value": "50",
23 | /// "total": "100"
24 | /// }
25 | /// ```
26 | ///
27 | /// - Note: This is a internal view, you should not use this directly. \
28 | /// Use ``DynamicUI`` instead.
29 | struct DynamicProgressView: View {
30 | @Environment(\.internalDynamicUIEnvironment)
31 | /// Internal: dynamicUIEnvironment
32 | private var dynamicUIEnvironment
33 |
34 | /// The component to display
35 | private let component: DynamicUIComponent
36 |
37 | /// Initialize the DynamicProgressView
38 | init(_ component: DynamicUIComponent) {
39 | self.component = component
40 | }
41 |
42 | /// Generated body for SwiftUI
43 | var body: some View {
44 | ProgressView(
45 | "\(component.title ?? "")",
46 | value: component.defaultValue?.toDouble() ?? 0,
47 | total: component.maximumValue ?? 1.0
48 | )
49 | .set(modifiers: component)
50 | }
51 | }
52 |
53 | #if DEBUG
54 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
55 | #Preview("ProgressView") {
56 | let json = """
57 | [
58 | {
59 | "type": "Form",
60 | "children": [
61 | {
62 | "type": "Section",
63 | "title": "ProgressView example",
64 | "children": [
65 | {
66 | "type": "ProgressView",
67 | "title": "ProgressView",
68 | "defaultValue": 0.5
69 | }
70 | ]
71 | }
72 | ]
73 | }
74 | ]
75 | """
76 |
77 | DynamicUI(json: json, component: .constant(nil))
78 | }
79 | #endif
80 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicText.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicText.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 19/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: Text
14 | ///
15 | /// DynamicText is a SwiftUI View that can be used to display an Text.
16 | ///
17 | ///
18 | /// JSON Example:
19 | /// ```json
20 | /// {
21 | /// "type": "Text",
22 | /// "title": "Title"
23 | /// }
24 | /// ```
25 | ///
26 | /// - Note: This is a internal view, you should not use this directly. \
27 | /// Use ``DynamicUI`` instead.
28 | struct DynamicText: View {
29 | @Environment(\.internalDynamicUIEnvironment)
30 | /// Internal: dynamicUIEnvironment
31 | private var dynamicUIEnvironment
32 |
33 | /// The component to display
34 | private let component: DynamicUIComponent
35 |
36 | /// Initialize the DynamicText
37 | init(_ component: DynamicUIComponent) {
38 | self.component = component
39 | }
40 |
41 | /// Generated body for SwiftUI
42 | var body: some View {
43 | Text(.init(component.title ?? ""))
44 | .set(modifiers: component)
45 | }
46 | }
47 |
48 | #if DEBUG
49 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
50 | #Preview("Text") {
51 | let json = """
52 | [
53 | {
54 | "type": "Form",
55 | "children": [
56 | {
57 | "type": "Section",
58 | "title": "Form example",
59 | "children": [
60 | {
61 | "type": "Text",
62 | "title": "This is inside a form"
63 | }
64 | ]
65 | },
66 | {
67 | "type": "Section",
68 | "children": [
69 | {
70 | "type": "Text",
71 | "title": "This is inside a form"
72 | }
73 | ]
74 | }
75 | ]
76 | }
77 | ]
78 | """
79 |
80 | DynamicUI(json: json, component: .constant(nil))
81 | }
82 | #endif
83 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicDisclosureGroup.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicDisclosureGroup.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 19/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: DisclosureGroup
14 | ///
15 | /// DynamicDisclosureGroup is a SwiftUI View that can be used to display an DisclosureGroup.
16 | ///
17 | /// JSON Example:
18 | /// ```json
19 | /// {
20 | /// "type": "DisclosureGroup",
21 | /// "children": [ ]
22 | /// }
23 | /// ```
24 | ///
25 | /// - Note: This is a internal view, you should not use this directly. \
26 | /// Use ``DynamicUI`` instead.
27 | struct DynamicDisclosureGroup: View {
28 | @Environment(\.internalDynamicUIEnvironment)
29 | /// Internal: dynamicUIEnvironment
30 | private var dynamicUIEnvironment
31 |
32 | /// The component to display
33 | private let component: DynamicUIComponent
34 |
35 | /// Initialize the DynamicDisclosureGroup
36 | init(_ component: DynamicUIComponent) {
37 | self.component = component
38 | }
39 |
40 | /// Generated body for SwiftUI
41 | var body: some View {
42 | #if !os(tvOS) && !os(watchOS)
43 | DisclosureGroup("\(component.title ?? "")") {
44 | if let children = component.children {
45 | AnyView(dynamicUIEnvironment.buildView(for: children))
46 | }
47 | }
48 | .set(modifiers: component)
49 | #else
50 | DynamicVStack(component)
51 | #endif
52 | }
53 | }
54 |
55 | #if DEBUG
56 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
57 | #Preview("DisclosureGroup") {
58 | let json = """
59 | [
60 | {
61 | "type": "Form",
62 | "title": "DisclosureGroup",
63 | "children": [
64 | {
65 | "type": "DisclosureGroup",
66 | "title": "DisclosureGroup",
67 | "children": [
68 | {
69 | "type": "Text",
70 | "title": "This is inside a form"
71 | }
72 | ]
73 | }
74 | ]
75 | }
76 | ]
77 | """
78 |
79 | DynamicUI(json: json, component: .constant(nil))
80 | }
81 | #endif
82 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicGroup.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicGroup.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 09/10/2025.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: GroupBox
14 | ///
15 | /// DynamicGroupBox is a SwiftUI View that can be used to display an GroupBox.
16 | ///
17 | /// JSON Example:
18 | /// ```json
19 | /// {
20 | /// "type": "Group",
21 | /// "children": [ ]
22 | /// }
23 | /// ```
24 | ///
25 | /// - Note: This is a internal view, you should not use this directly. \
26 | /// Use ``DynamicUI`` instead.
27 | struct DynamicGroup: View {
28 | @Environment(\.internalDynamicUIEnvironment)
29 | /// Internal: dynamicUIEnvironment
30 | private var dynamicUIEnvironment
31 |
32 | /// The component to display
33 | private let component: DynamicUIComponent
34 |
35 | /// Initialize the DynamicGroupBox
36 | init(_ component: DynamicUIComponent) {
37 | self.component = component
38 | }
39 |
40 | /// Generated body for SwiftUI
41 | var body: some View {
42 | #if !os(tvOS) && !os(watchOS)
43 | Group {
44 | if let children = component.children {
45 | AnyView(dynamicUIEnvironment.buildView(for: children))
46 | }
47 | }
48 | .set(modifiers: component)
49 | #else
50 | DynamicVStack(component)
51 | #endif
52 | }
53 | }
54 |
55 | #if DEBUG
56 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
57 | #Preview("Group") {
58 | let json = """
59 | [
60 | {
61 | "type": "Form",
62 | "children": [
63 | {
64 | "type": "Section",
65 | "title": "Group example",
66 | "children": [
67 | {
68 | "type": "Group",
69 | "children": [
70 | {
71 | "type":"Text",
72 | "title": "Text"
73 | }
74 | ]
75 | }
76 | ]
77 | }
78 | ]
79 | }
80 | ]
81 | """
82 |
83 | DynamicUI(json: json, component: .constant(nil))
84 | }
85 | #endif
86 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicForm.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicForm.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 19/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: Form
14 | ///
15 | /// DynamicForm is a SwiftUI View that can be used to display an Form.
16 | ///
17 | /// JSON Example:
18 | /// ```json
19 | /// {
20 | /// "type": "Form",
21 | /// "children": [ ]
22 | /// }
23 | /// ```
24 | ///
25 | /// - Note: This is a internal view, you should not use this directly. \
26 | /// Use ``DynamicUI`` instead.
27 | struct DynamicForm: View {
28 | @Environment(\.internalDynamicUIEnvironment)
29 | /// Internal: dynamicUIEnvironment
30 | private var dynamicUIEnvironment
31 |
32 | /// The component to display
33 | private let component: DynamicUIComponent
34 |
35 | /// Initialize the DynamicForm
36 | init(_ component: DynamicUIComponent) {
37 | self.component = component
38 | }
39 |
40 | /// Generated body for SwiftUI
41 | var body: some View {
42 | Form {
43 | if let children = component.children {
44 | AnyView(dynamicUIEnvironment.buildView(for: children))
45 | }
46 | }
47 | .set(modifiers: component)
48 | }
49 | }
50 |
51 | #if DEBUG
52 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
53 | #Preview("Form") {
54 | let json = """
55 | [
56 | {
57 | "type": "Form",
58 | "children": [
59 | {
60 | "type": "Section",
61 | "title": "Form example",
62 | "children": [
63 | {
64 | "type": "Text",
65 | "title": "This is inside a form"
66 | }
67 | ]
68 | },
69 | {
70 | "type": "Section",
71 | "children": [
72 | {
73 | "type": "Text",
74 | "title": "This is inside a form"
75 | }
76 | ]
77 | }
78 | ]
79 | }
80 | ]
81 | """
82 |
83 | DynamicUI(json: json, component: .constant(nil))
84 | }
85 | #endif
86 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicGroupBox.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicGroupBox.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 19/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: GroupBox
14 | ///
15 | /// DynamicGroupBox is a SwiftUI View that can be used to display an GroupBox.
16 | ///
17 | /// JSON Example:
18 | /// ```json
19 | /// {
20 | /// "type": "GroupBox",
21 | /// "children": [ ]
22 | /// }
23 | /// ```
24 | ///
25 | /// - Note: This is a internal view, you should not use this directly. \
26 | /// Use ``DynamicUI`` instead.
27 | struct DynamicGroupBox: View {
28 | @Environment(\.internalDynamicUIEnvironment)
29 | /// Internal: dynamicUIEnvironment
30 | private var dynamicUIEnvironment
31 |
32 | /// The component to display
33 | private let component: DynamicUIComponent
34 |
35 | /// Initialize the DynamicGroupBox
36 | init(_ component: DynamicUIComponent) {
37 | self.component = component
38 | }
39 |
40 | /// Generated body for SwiftUI
41 | var body: some View {
42 | #if !os(tvOS) && !os(watchOS)
43 | GroupBox {
44 | if let children = component.children {
45 | AnyView(dynamicUIEnvironment.buildView(for: children))
46 | }
47 | }
48 | .set(modifiers: component)
49 | #else
50 | DynamicVStack(component)
51 | #endif
52 | }
53 | }
54 |
55 | #if DEBUG
56 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
57 | #Preview("Groupbox") {
58 | let json = """
59 | [
60 | {
61 | "type": "Form",
62 | "children": [
63 | {
64 | "type": "Section",
65 | "title": "GroupBox example",
66 | "children": [
67 | {
68 | "type": "GroupBox",
69 | "children": [
70 | {
71 | "type":"Text",
72 | "title": "Text"
73 | }
74 | ]
75 | }
76 | ]
77 | }
78 | ]
79 | }
80 | ]
81 | """
82 |
83 | DynamicUI(json: json, component: .constant(nil))
84 | }
85 | #endif
86 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicList.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicList.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 19/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: List
14 | ///
15 | /// DynamicList is a SwiftUI View that can be used to display an List.
16 | ///
17 | /// JSON Example:
18 | /// ```json
19 | /// {
20 | /// "type": "List",
21 | /// "children": [ ]
22 | /// }
23 | /// ```
24 | ///
25 | /// - Note: This is a internal view, you should not use this directly. \
26 | /// Use ``DynamicUI`` instead.
27 | struct DynamicList: View {
28 | @Environment(\.internalDynamicUIEnvironment)
29 | /// Internal: dynamicUIEnvironment
30 | private var dynamicUIEnvironment
31 |
32 | /// The component to display
33 | private let component: DynamicUIComponent
34 |
35 | /// Initialize the DynamicList
36 | init(_ component: DynamicUIComponent) {
37 | self.component = component
38 | }
39 |
40 | /// Generated body for SwiftUI
41 | var body: some View {
42 | List {
43 | if let children = component.children {
44 | AnyView(dynamicUIEnvironment.buildView(for: children))
45 | }
46 | }
47 | .set(modifiers: component)
48 | }
49 | }
50 |
51 | #if DEBUG
52 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
53 | #Preview("List") {
54 | let json = """
55 | [
56 | {
57 | "type": "List",
58 | "children": [
59 | {
60 | "type": "Section",
61 | "title": "List example",
62 | "children": [
63 | {
64 | "type": "Text",
65 | "title": "This is inside a list"
66 | }
67 | ]
68 | },
69 | {
70 | "type": "Section",
71 | "children": [
72 | {
73 | "type": "Text",
74 | "title": "This is inside a list"
75 | }
76 | ]
77 | }
78 | ]
79 | }
80 | ]
81 | """
82 |
83 | DynamicUI(json: json, component: .constant(nil))
84 | }
85 | #endif
86 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicGauge.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicGauge.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 19/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: Gauge
14 | ///
15 | /// ⚠ DynamicGauge is a SwiftUI View that can be used to display an Gauge.
16 | ///
17 | /// JSON Example:
18 | /// ```json
19 | /// {
20 | /// "type": "Gauge",
21 | /// "title": "Title",
22 | /// "defaultValue": 0.5
23 | /// }
24 | /// ```
25 | ///
26 | /// - Warning: This component is not finished yet.
27 | ///
28 | /// - Note: This is a internal view, you should not use this directly. \
29 | /// Use ``DynamicUI`` instead.
30 | struct DynamicGauge: View {
31 | @Environment(\.internalDynamicUIEnvironment)
32 | /// Internal: dynamicUIEnvironment
33 | private var dynamicUIEnvironment
34 |
35 | /// The component to display
36 | private let component: DynamicUIComponent
37 |
38 | @State
39 | /// The state of the Gauge
40 | private var state: Double
41 |
42 | /// Initialize the DynamicGauge
43 | init(_ component: DynamicUIComponent) {
44 | self.state = component.defaultValue?.toDouble() ?? 0
45 | self.component = component
46 | }
47 |
48 | /// Generated body for SwiftUI
49 | var body: some View {
50 | #if !os(tvOS)
51 | if #available(macOS 13.0, iOS 16.0, *) {
52 | Gauge(value: state) {
53 | Text("\(component.title ?? "")")
54 | }
55 | .set(modifiers: component)
56 | } else {
57 | EmptyView()
58 | }
59 | #else
60 | EmptyView()
61 | #endif
62 | }
63 | }
64 |
65 | #if DEBUG
66 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
67 | #Preview("Gauge") {
68 | let json = """
69 | [
70 | {
71 | "type": "Form",
72 | "children": [
73 | {
74 | "type": "Section",
75 | "title": "Gauge example",
76 | "children": [
77 | {
78 | "type": "Gauge",
79 | "title": "Gauge",
80 | "defaultValue": 0.5
81 | }
82 | ]
83 | }
84 | ]
85 | }
86 | ]
87 | """
88 |
89 | DynamicUI(json: json, component: .constant(nil))
90 | }
91 | #endif
92 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicSlider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicSlider.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 19/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: Slider
14 | ///
15 | /// The DynamicSlider is a SwiftUI View that can be used to display a slider.
16 | ///
17 | /// JSON Example:
18 | /// ```json
19 | /// {
20 | /// "type": "Slider",
21 | /// "title": "Title",
22 | /// "minLabel": "0",
23 | /// "maxLabel": "100"
24 | /// }
25 | /// ```
26 | ///
27 | /// - Note: This is a internal view, you should not use this directly. \
28 | /// Use ``DynamicUI`` instead.
29 | struct DynamicSlider: View {
30 | @Environment(\.internalDynamicUIEnvironment)
31 | /// Internal: dynamicUIEnvironment
32 | private var dynamicUIEnvironment
33 |
34 | @State
35 | /// The state of the Slider
36 | private var state: Double
37 |
38 | /// The component to display
39 | private let component: DynamicUIComponent
40 |
41 | /// Initialize the DynamicSlider
42 | init(_ component: DynamicUIComponent) {
43 | self.state = component.defaultValue?.toDouble() ?? 0
44 | self.component = component
45 | }
46 |
47 | /// Generated body for SwiftUI
48 | var body: some View {
49 | #if !os(tvOS)
50 | Slider(value: $state.onChange({ newState in
51 | var newComponent = component
52 | newComponent.state = .double(newState)
53 |
54 | dynamicUIEnvironment.component = newComponent
55 | })) {
56 | Text("\(component.title ?? "")")
57 | } minimumValueLabel: {
58 | Text("\(component.minimum ?? "")")
59 | } maximumValueLabel: {
60 | Text("\(component.maximum ?? "")")
61 | }
62 | .set(modifiers: component)
63 | #else
64 | EmptyView()
65 | #endif
66 | }
67 | }
68 |
69 | #if DEBUG
70 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
71 | #Preview("Slider") {
72 | let json = """
73 | [
74 | {
75 | "type": "Form",
76 | "children": [
77 | {
78 | "type": "Slider",
79 | "title": "Form example",
80 | "defaultValue": 0.5,
81 | "minimum": "Minimum",
82 | "maximum": "Maximum"
83 | }
84 | ]
85 | }
86 | ]
87 | """
88 |
89 | DynamicUI(json: json, component: .constant(nil))
90 | }
91 | #endif
92 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicHStack.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicHStack.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 19/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: HStack
14 | ///
15 | /// DynamicHStack is a SwiftUI View that can be used to display an HStack.
16 | ///
17 | /// JSON Example:
18 | /// ```json
19 | /// {
20 | /// "type": "HStack",
21 | /// "children": [ ]
22 | /// }
23 | /// ```
24 | ///
25 | /// - Note: This is a internal view, you should not use this directly. \
26 | /// Use ``DynamicUI`` instead.
27 | struct DynamicHStack: View {
28 | @Environment(\.internalDynamicUIEnvironment)
29 | /// Internal: dynamicUIEnvironment
30 | private var dynamicUIEnvironment
31 |
32 | /// The component to display
33 | private let component: DynamicUIComponent
34 |
35 | /// Initialize the DynamicHStack
36 | init(_ component: DynamicUIComponent) {
37 | self.component = component
38 | }
39 |
40 | /// Generated body for SwiftUI
41 | var body: some View {
42 | HStack {
43 | if let children = component.children {
44 | AnyView(dynamicUIEnvironment.buildView(for: children))
45 | }
46 | }
47 | .set(modifiers: component)
48 | }
49 | }
50 |
51 | #if DEBUG
52 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
53 | #Preview("HStack") {
54 | let json = """
55 | [
56 | {
57 | "type": "HStack",
58 | "children": [
59 | {
60 | "type": "VStack",
61 | "children": [
62 | {
63 | "type": "Text",
64 | "title": "Text 1",
65 | }
66 | ],
67 | "padding": true,
68 | "modifiers": {
69 | "background": "blue"
70 | }
71 | },
72 | {
73 | "type": "VStack",
74 | "children": [
75 | {
76 | "type": "Text",
77 | "title": "Text 2",
78 | }
79 | ],
80 | "padding": true,
81 | "modifiers": {
82 | "background": "red"
83 | }
84 | }
85 | ]
86 | }
87 | ]
88 | """
89 |
90 | DynamicUI(json: json, component: .constant(nil))
91 | }
92 | #endif
93 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicSection.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicSection.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 19/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: Section
14 | ///
15 | /// DynamicSection is a SwiftUI View that can be used to display an Section.
16 | ///
17 | /// JSON Example:
18 | /// ```json
19 | /// {
20 | /// "type": "Section",
21 | /// "title": "Title"
22 | /// }
23 | /// ```
24 | ///
25 | /// - Note: This is a internal view, you should not use this directly. \
26 | /// Use ``DynamicUI`` instead.
27 | struct DynamicSection: View {
28 | @Environment(\.internalDynamicUIEnvironment)
29 | /// Internal: dynamicUIEnvironment
30 | private var dynamicUIEnvironment
31 |
32 | /// The component to display
33 | private let component: DynamicUIComponent
34 |
35 | /// Initialize the DynamicSection
36 | init(_ component: DynamicUIComponent) {
37 | self.component = component
38 | }
39 |
40 | /// Helper to create children view
41 | @ViewBuilder
42 | private var childrenView: some View {
43 | if let children = component.children {
44 | AnyView(dynamicUIEnvironment.buildView(for: children))
45 | }
46 | }
47 |
48 | /// Generated body for SwiftUI
49 | var body: some View {
50 | if let title = component.title, !title.isEmpty {
51 | Section(header: Text(title)) {
52 | childrenView
53 | }
54 | .set(modifiers: component)
55 | } else {
56 | Section {
57 | childrenView
58 | }
59 | .set(modifiers: component)
60 | }
61 | }
62 | }
63 |
64 | #if DEBUG
65 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
66 | #Preview("Section") {
67 | let json = """
68 | [
69 | {
70 | "type": "Form",
71 | "children": [
72 | {
73 | "type": "Section",
74 | "title": "Form example",
75 | "children": [
76 | {
77 | "type": "Text",
78 | "title": "This is inside a form"
79 | }
80 | ]
81 | },
82 | {
83 | "type": "Section",
84 | "children": [
85 | {
86 | "type": "Text",
87 | "title": "This is inside a form"
88 | }
89 | ]
90 | }
91 | ]
92 | }
93 | ]
94 | """
95 |
96 | DynamicUI(json: json, component: .constant(nil))
97 | }
98 | #endif
99 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUIComponent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicUIComponent.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 16/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// This struct constructs a UI Component from JSON.
14 | public struct DynamicUIComponent: Codable, Hashable {
15 | /// Type of component
16 | ///
17 | /// This is the evaqulent of a SwiftUI View
18 | public let type: String
19 |
20 | /// Title/Label of component
21 | public let title: String?
22 |
23 | /// Text within component (if any)
24 | public let text: String?
25 |
26 | /// Component identifier
27 | ///
28 | /// The component identifier can be used to have an identifier if you need react on callback calls
29 | /// This is optional but recommended if you use a event handler function
30 | public let identifier: String?
31 |
32 | /// Event handler
33 | ///
34 | /// The event handler is called when the component is interacted with.
35 | /// This can be a button press, a slider change, etc.
36 | public let eventHandler: String?
37 |
38 | /// Default value of component
39 | public let defaultValue: AnyCodable?
40 |
41 | /// Modifiers to components (not yet used)
42 | public var modifiers: [String: AnyCodable]?
43 |
44 | /// Parameters of component (not yet used)
45 | public let parameters: [String: AnyCodable]?
46 |
47 | /// URL
48 | public let url: String?
49 |
50 | /// Children (used in VStack, HStack, ZStack)
51 | public let children: [DynamicUIComponent]?
52 |
53 | // TODO: Find a way to move this to parameters
54 | /// Minimum value description
55 | ///
56 | /// - Note: This may be removed in the future in favor of ``UIComponent.parameters``
57 | public let minimum: String?
58 |
59 | // TODO: Find a way to move this to parameters
60 | /// Minumum value
61 | ///
62 | /// - Note: This may be removed in the future in favor of ``UIComponent.parameters``
63 | public let minimumValue: Double?
64 |
65 | // TODO: Find a way to move this to parameters
66 | /// Maximum value description
67 | ///
68 | /// - Note: This may be removed in the future in favor of ``UIComponent.parameters``
69 | public let maximum: String?
70 |
71 | // TODO: Find a way to move this to parameters
72 | /// Maximum value
73 | ///
74 | /// - Note: This may be removed in the future in favor of ``UIComponent.parameters``
75 | public let maximumValue: Double?
76 |
77 | /// Is the component disabled?
78 | public var disabled: Bool? = false
79 |
80 | /// The current state of an element
81 | ///
82 | /// The state can mean the state (on/off), but in case of a text field it can also mean the value of the text field.
83 | ///
84 | /// - Note: Do not init state in your UIComponent unless needed.
85 | public var state: AnyCodable?
86 | }
87 |
--------------------------------------------------------------------------------
/Playground/DynamicUI Playground/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // DynamicUI Playground
4 | //
5 | // Created by Wesley de Groot on 18/07/2024.
6 | //
7 |
8 | import SwiftUI
9 | import DynamicUI
10 |
11 | struct ContentView: View {
12 | @State
13 | var text: String = """
14 | [
15 | {
16 | "type": "Form",
17 | "children": [
18 | {
19 | "type": "Section",
20 | "title": "Form example",
21 | "children": [
22 | {
23 | "type": "Text",
24 | "title": "This is inside a form"
25 | },
26 | {
27 | "type": "Button",
28 | "title": "Click me",
29 | "eventHandler": "customHandler"
30 | },
31 | {
32 | "type": "Toggle",
33 | "title": "Toggle me",
34 | "identifier": "my.toggle.1"
35 | },
36 | {
37 | "type": "Text",
38 | "title": "_Wait_, am i generating views from JSON?",
39 | "modifiers": {
40 | "foregroundStyle": "red",
41 | "opacity": 0.5
42 | },
43 | },
44 | {
45 | "type": "Label",
46 | "title": "Shine",
47 | "url": "star"
48 | }
49 | ]
50 | }
51 | ]
52 | }
53 | ]
54 | """
55 |
56 | @State
57 | private var log: String = "\n\n\n"
58 |
59 | @State
60 | private var component: DynamicUIComponent?
61 |
62 | @State
63 | private var error: Error?
64 |
65 | var body: some View {
66 | VStack(spacing: 1) {
67 | HStack(spacing: 1) {
68 | TextEditor(text: $text)
69 | .frame(maxWidth: .infinity)
70 | .border(error == nil ? .green : .red)
71 |
72 | Divider()
73 |
74 | DynamicUI(
75 | json: text,
76 | component: $component,
77 | error: $error
78 | )
79 | .frame(maxWidth: .infinity, maxHeight: .infinity)
80 | .id(text) // To update if text is changed
81 | .border(.purple)
82 | }
83 | Divider()
84 | VStack {
85 | Text(log)
86 | .frame(maxWidth: .infinity)
87 | .minimumScaleFactor(0.1)
88 | }
89 | .border(.blue)
90 | .frame(height: 75)
91 | .frame(maxWidth: .infinity)
92 | }
93 | .onChange(of: component) {
94 | log = "\(component)"
95 | }
96 | }
97 | }
98 |
99 | #Preview {
100 | ContentView()
101 | }
102 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicToggle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicToggle.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 16/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: Toggle
14 | ///
15 | /// The DynamicToggle is a SwiftUI View that can be used to display a toggle.
16 | ///
17 | /// JSON Example:
18 | /// ```json
19 | /// {
20 | /// "type": "Toggle",
21 | /// "title": "Title",
22 | /// "defaultValue": true
23 | /// }
24 | /// ```
25 | ///
26 | /// - Note: This is a internal view, you should not use this directly. \
27 | /// Use ``DynamicUI`` instead.
28 | struct DynamicToggle: View {
29 | @Environment(\.internalDynamicUIEnvironment)
30 | /// Internal: dynamicUIEnvironment
31 | var dynamicUIEnvironment
32 |
33 | @State
34 | /// The state of the Toggle
35 | private var state: Bool
36 |
37 | /// The title of the Toggle
38 | private let title: String
39 |
40 | /// The component to display
41 | private let component: DynamicUIComponent
42 |
43 | /// Initialize the DynamicToggle
44 | init(_ component: DynamicUIComponent) {
45 | self.title = component.title ?? ""
46 | self.state = component.defaultValue?.toBool() ?? false
47 | self.component = component
48 | }
49 |
50 | /// Generated body for SwiftUI
51 | var body: some View {
52 | Toggle(isOn: $state.onChange({ newState in
53 | var newComponent = component
54 | newComponent.state = .bool(newState)
55 |
56 | dynamicUIEnvironment.component = newComponent
57 | })) {
58 | Text(title)
59 | }
60 | .set(modifiers: component)
61 | }
62 | }
63 |
64 | #if DEBUG
65 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
66 | #Preview("Toggle") {
67 | let json = """
68 | [
69 | {
70 | "type": "Form",
71 | "children": [
72 | {
73 | "type": "Section",
74 | "title": "Form example",
75 | "children": [
76 | {
77 | "type": "Toggle",
78 | "title": "Toggle"
79 | },
80 | {
81 | "type": "Toggle",
82 | "title": "Toggle",
83 | "defaultValue": true
84 | },
85 | {
86 | "type": "Toggle",
87 | "title": "Toggle",
88 | "defaultValue": true,
89 | "disabled": true
90 | }
91 | ]
92 | }
93 | ]
94 | }
95 | ]
96 | """
97 |
98 | DynamicUI(json: json, component: .constant(nil))
99 | }
100 | #endif
101 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Views/DynamicHSplitView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicHSplitView.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 19/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | /// DynamicUI: HSplitView
14 | ///
15 | /// DynamicHSplitView is a SwiftUI View that can be used to display an HSplitView.
16 | /// JSON Example:
17 | /// ```json
18 | /// {
19 | /// "type": "HSplitView",
20 | /// "children": [ ]
21 | /// }
22 | /// ```
23 | ///
24 | /// - Note: This is a internal view, you should not use this directly. \
25 | /// Use ``DynamicUI`` instead.
26 | struct DynamicHSplitView: View {
27 | @Environment(\.internalDynamicUIEnvironment)
28 | /// Internal: dynamicUIEnvironment
29 | private var dynamicUIEnvironment
30 |
31 | /// The component to display
32 | private let component: DynamicUIComponent
33 |
34 | /// Initialize the DynamicHSplitView
35 | init(_ component: DynamicUIComponent) {
36 | self.component = component
37 | }
38 |
39 | /// Generated body for SwiftUI
40 | var body: some View {
41 | #if os(macOS)
42 | HSplitView {
43 | if let children = component.children {
44 | AnyView(dynamicUIEnvironment.buildView(for: children))
45 | }
46 | }
47 | .set(modifiers: component)
48 | #else
49 | HStack {
50 | if let children = component.children {
51 | AnyView(dynamicUIEnvironment.buildView(for: children))
52 | }
53 | }
54 | .set(modifiers: component)
55 | #endif
56 | }
57 | }
58 |
59 | #if DEBUG
60 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
61 | #Preview("HSplitView") {
62 | let json = """
63 | [
64 | {
65 | "type": "HSplitView",
66 | "children": [
67 | {
68 | "type": "VStack",
69 | "children": [
70 | {
71 | "type": "Text",
72 | "title": "Text 1",
73 | }
74 | ],
75 | "padding": true,
76 | "modifiers": {
77 | "background": "blue"
78 | }
79 | },
80 | {
81 | "type": "VStack",
82 | "children": [
83 | {
84 | "type": "Text",
85 | "title": "Text 2",
86 | }
87 | ],
88 | "padding": true,
89 | "modifiers": {
90 | "background": "red"
91 | }
92 | }
93 | ]
94 | }
95 | ]
96 | """
97 |
98 | DynamicUI(json: json, component: .constant(nil))
99 | }
100 | #endif
101 |
--------------------------------------------------------------------------------
/Playground/DynamicUI Playground.xcodeproj/xcshareddata/xcschemes/DynamicUI Playground.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
9 |
10 |
16 |
22 |
23 |
24 |
25 |
26 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Helpers/DynamicUIHelper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicUIHelper.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 25/07/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 |
13 | // swiftlint:disable cyclomatic_complexity function_body_length
14 | /// DynamicUIHelper
15 | ///
16 | /// DynamicUIHelper helps to translate Strings to native SwiftUI .context
17 | class DynamicUIHelper {
18 |
19 | /// Translate string colors to native ``Color``.
20 | ///
21 | /// - Parameter input: Color as string
22 | ///
23 | /// - Returns: SwiftUI ``Color``
24 | static func translateColor(_ input: String) -> Color? {
25 | switch input.lowercased() {
26 | case "red":
27 | return .red
28 |
29 | case "orange":
30 | return .orange
31 |
32 | case "yellow":
33 | return .yellow
34 |
35 | case "green":
36 | return .green
37 |
38 | case "mint":
39 | if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) {
40 | return .mint
41 | }
42 | return .primary
43 |
44 | case "teal":
45 | if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) {
46 | return .teal
47 | }
48 | return .primary
49 |
50 | case "cyan":
51 | if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) {
52 | return .cyan
53 | }
54 | return .primary
55 |
56 | case "blue":
57 | return .blue
58 |
59 | case "indigo":
60 | if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) {
61 | return .indigo
62 | }
63 | return .primary
64 |
65 | case "purple":
66 | return .purple
67 |
68 | case "pink":
69 | return .pink
70 |
71 | case "brown":
72 | if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) {
73 | return .brown
74 | }
75 | return .primary
76 |
77 | case "white":
78 | return .white
79 |
80 | case "gray":
81 | return .gray
82 |
83 | case "black":
84 | return .black
85 |
86 | case "clear":
87 | return .clear
88 |
89 | case "primary":
90 | return .primary
91 |
92 | case "secondary":
93 | return .secondary
94 |
95 | default:
96 | return .primary
97 | }
98 | }
99 |
100 | /// Translate a string font weight to a native ``Font.Weight``
101 | ///
102 | /// - Parameter input: Font weight as string
103 | ///
104 | /// - Returns: Translated ``Font.Weight``
105 | static func translateFontWeight(_ input: String) -> Font.Weight? {
106 | switch input {
107 | case "ultraLight":
108 | return .ultraLight
109 |
110 | case "thin":
111 | return .thin
112 |
113 | case "light":
114 | return .light
115 |
116 | case "regular":
117 | return .regular
118 |
119 | case "medium":
120 | return .medium
121 |
122 | case "semibold":
123 | return .semibold
124 |
125 | case "bold":
126 | return .bold
127 |
128 | case "heavy":
129 | return .heavy
130 |
131 | case "black":
132 | return .black
133 |
134 | default:
135 | return .regular
136 | }
137 | }
138 |
139 | static func translateAlignment(_ input: String?) -> Alignment {
140 | switch input {
141 | case "leading":
142 | return .leading
143 | case "center":
144 | return .center
145 | case "trailing":
146 | return .trailing
147 | default:
148 | return .center
149 | }
150 | }
151 | }
152 |
153 | // swiftlint:enable cyclomatic_complexity function_body_length
154 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DynamicUI
2 |
3 | Create a SwiftUI user interface through a JSON file. The JSON file will contain the structure of the user interface, and the program will create the user interface based on the JSON file.
4 |
5 | [](https://swiftpackageindex.com/0xWDG/DynamicUI) [](https://swiftpackageindex.com/0xWDG/DynamicUI)
6 | [](https://swift.org/package-manager)
7 | 
8 |
9 | ## Requirements
10 |
11 | - Swift 5.9+ (Xcode 15+)
12 | - iOS 15+, macOS 12+, tvOS 14+, watchOS 7+, macCatalyst 15+, visionOS 1.0+
13 |
14 | ## Installation
15 |
16 | Install using Swift Package Manager
17 |
18 | ```swift
19 | dependencies: [
20 | .package(url: "https://github.com/0xWDG/DynamicUI.git", branch: "main"),
21 | ],
22 | targets: [
23 | .target(name: "MyTarget", dependencies: [
24 | .product(name: "DynamicUI", package: "DynamicUI"),
25 | ]),
26 | ]
27 | ```
28 |
29 | And import it:
30 |
31 | ```swift
32 | import DynamicUI
33 | ```
34 |
35 | ## Usage
36 |
37 | ```swift
38 | import SwiftUI
39 | import DynamicUI
40 |
41 | struct ContentView: View {
42 | let json = """
43 | [
44 | {
45 | "type": "Text",
46 | "title": "Wait, am i generating views from JSON?",
47 | "modifiers": {"foregroundStyle":"red","opacity":0.6}
48 | },
49 | {
50 | "type": "Button",
51 | "title": "Click me",
52 | "eventHandler": "customHandler"
53 | },
54 | {
55 | "type": "Toggle",
56 | "title": "Toggle me",
57 | "identifier": "my.toggle.1"
58 | }
59 | ]
60 | """.data(using: .utf8)
61 |
62 | @State private var component: DynamicUIComponent?
63 | @State private var error: Error?
64 |
65 | var body: some View {
66 | DynamicUI(
67 | json: json,
68 | component: $component,
69 | error: $error
70 | )
71 | }
72 | }
73 | ```
74 |
75 | ## Usage (Legacy)
76 |
77 | ```swift
78 | import SwiftUI
79 | import DynamicUI
80 |
81 | struct ContentView: View {
82 | let json = """
83 | [
84 | {
85 | "type": "Text",
86 | "title": "Wait, am i generating views from JSON?",
87 | "modifiers": {"foregroundStyle":"red","opacity":0.6}
88 | },
89 | {
90 | "type": "Button",
91 | "title": "Click me",
92 | "eventHandler": "customHandler"
93 | },
94 | {
95 | "type": "Toggle",
96 | "title": "Toggle me",
97 | "identifier": "my.toggle.1"
98 | }
99 | ]
100 | """.data(using: .utf8)
101 |
102 | @State private var error: Error?
103 |
104 | var body: some View {
105 | DynamicUI(
106 | json: json,
107 | callback: { component in
108 | // This contains everything passed as a component (type, title, identifier, ...)
109 | print(component)
110 | },
111 | error: $error
112 | )
113 | }
114 | }
115 | ```
116 |
117 | ### Playground Application:
118 |
119 | In the directory `Playground` is a Xcode project to build the [Playground Application](#Playground)
120 | The playground application is available for macOS, iOS, watchOS, tvOS and visionOS.
121 |
122 | ## Supported SwiftUI Views
123 |
124 | See the list in the [documentation over here](https://0xwdg.github.io/DynamicUI)
125 |
126 | ## Images
127 |
128 | ### Playground
129 |
130 |
131 |
132 |
133 | ### V0.0.1 in action
134 |
135 |
136 |
137 | ## Used By
138 |
139 | - [Aurora Editor](https://github.com/AuroraEditor/AuroraEditor) for custom views in extensions.
140 |
141 | ## Contact
142 |
143 | 🦋 [@0xWDG](https://bsky.app/profile/0xWDG.bsky.social)
144 | 🐘 [mastodon.social/@0xWDG](https://mastodon.social/@0xWDG)
145 | 🐦 [@0xWDG](https://x.com/0xWDG)
146 | 🧵 [@0xWDG](https://www.threads.net/@0xWDG)
147 | 🌐 [wesleydegroot.nl](https://wesleydegroot.nl)
148 | 🤖 [Discord](https://discordapp.com/users/918438083861573692)
149 |
--------------------------------------------------------------------------------
/Playground/DynamicUI Playground.xcodeproj/xcshareddata/xcschemes/DynamicUI Watch App.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
9 |
10 |
16 |
22 |
23 |
24 |
30 |
36 |
37 |
38 |
39 |
40 |
46 |
47 |
50 |
56 |
57 |
58 |
61 |
67 |
68 |
69 |
70 |
71 |
81 |
83 |
89 |
90 |
91 |
92 |
98 |
100 |
106 |
107 |
108 |
109 |
111 |
112 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUI.docc/Modifiers.md:
--------------------------------------------------------------------------------
1 | # Supported Modifiers
2 |
3 | DynamicUI supports a variety of modifiers that can be applied to components to customize their appearance and behavior. Here are some examples:
4 |
5 | ## Color Modifiers
6 |
7 | You can change the foreground and background colors of a component using the `foregroundColor` and `backgroundColor` modifiers.
8 |
9 | ```json
10 | {
11 | "type": "Text",
12 | "title": "Title",
13 | "modifiers": {
14 | "foregroundColor": "purple",
15 | "backgroundColor": "yellow"
16 | }
17 | }
18 | ```
19 |
20 | ## fontWeight Modifier
21 |
22 | You can change the font weight of a component using the `fontWeight` modifier.
23 |
24 | ```json
25 | {
26 | "type": "Text",
27 | "title": "Title",
28 | "modifiers": {
29 | "fontWeight": "bold"
30 | }
31 | }
32 | ```
33 |
34 | ## Font Modifiers
35 |
36 | [TODO] You can customize the font of a component using the `font` modifier.
37 |
38 | ```json
39 | {
40 | "type": "Text",
41 | "title": "Title",
42 | "modifiers": {
43 | "font": {
44 | "size": 16,
45 | "weight": "bold"
46 | }
47 | }
48 | }
49 | ```
50 |
51 | ## Bold Modifier
52 |
53 | You can make the font of a component bold using the `bold` modifier.
54 |
55 | ```json
56 | {
57 | "type": "Text",
58 | "title": "Title",
59 | "modifiers": {
60 | "bold": true
61 | }
62 | }
63 | ```
64 |
65 | ## Italic Modifier
66 |
67 | You can make the font of a component italic using the `italic` modifier.
68 |
69 | ```json
70 | {
71 | "type": "Text",
72 | "title": "Title",
73 | "modifiers": {
74 | "italic": true
75 | }
76 | }
77 | ```
78 | ## Monospaced Modifier
79 |
80 | You can make the font of a component monospaced using the `monospaced` modifier.
81 |
82 | ```json
83 | {
84 | "type": "Text",
85 | "title": "Title",
86 | "modifiers": {
87 | "monospaced": true
88 | }
89 | }
90 | ```
91 |
92 | ## Frame Modifier
93 |
94 | You can set the frame of a component using the `frame` modifier.
95 |
96 | ```json
97 | {
98 | "type": "Text",
99 | "title": "Title",
100 | "modifiers": {
101 | "frame": {
102 | "width": 200,
103 | "height": 50,
104 | "alignment": "center"
105 | }
106 | }
107 | }
108 | ```
109 |
110 | #### Supported modifiers for the Frame Modifier:
111 |
112 | | Modifier | Type | Description |
113 | |--------------|--------|-------------------------------|
114 | | `width` | float | The width of the frame |
115 | | `height` | float | The height of the frame |
116 | | `minWidth` | float | The minimum width of the frame |
117 | | `idealWidth` | float | The ideal width of the frame |
118 | | `maxWidth` | float | The maximum width of the frame |
119 | | `minHeight` | float | The minimum height of the frame |
120 | | `idealHeight`| float | The ideal height of the frame |
121 | | `maxHeight` | float | The maximum height of the frame |
122 | | `alignment` | string | The alignment of the frame |
123 |
124 | #### Supported Values for `alignment`:
125 |
126 | | Value | Description |
127 | |----------------|---------------------------------|
128 | | `leading` | Aligns the content to the leading edge (left in LTR, right in RTL) |
129 | | `trailing` | Aligns the content to the trailing edge (right in LTR, left in RTL) |
130 | | `center` | Centers the content within the frame |
131 | | `top` | Aligns the content to the top of the frame |
132 | | `bottom` | Aligns the content to the bottom of the frame |
133 |
134 | ## Padding Modifier
135 |
136 | You can add padding to a component using the `padding` modifier.
137 |
138 | To use the default padding:
139 |
140 | ```json
141 | {
142 | "type": "Text",
143 | "title": "Title",
144 | "modifiers": {
145 | "padding": true
146 | }
147 | }
148 | ```
149 |
150 | A custom value:
151 |
152 | ```json
153 | {
154 | "type": "Text",
155 | "title": "Title",
156 | "modifiers": {
157 | "padding": 10
158 | }
159 | }
160 | ```
161 |
162 | [TODO] You can specify padding for specific edges:
163 |
164 | ```json
165 | {
166 | "type": "Text",
167 | "title": "Title",
168 | "modifiers": {
169 | "padding": {
170 | "top": 10,
171 | "bottom": 5,
172 | "leading": 15,
173 | "trailing": 20
174 | }
175 | }
176 | }
177 | ```
178 |
179 | ## Padding and Margins
180 |
181 | [TODO] You can add padding and margins to components using the `padding` and `margin` modifiers.
182 |
183 | ```json
184 | {
185 | "type": "Text",
186 | "title": "Title",
187 | "modifiers": {
188 | "padding": {
189 | "top": 10,
190 | "bottom": 10,
191 | "leading": 10,
192 | "trailing": 10
193 | },
194 | "margin": {
195 | "top": 10,
196 | "bottom": 10,
197 | "leading": 10,
198 | "trailing": 10
199 | }
200 | }
201 | }
202 | ```
203 |
204 | ## Shadow
205 |
206 | [TODO] You can add a shadow effect to components using the `shadow` modifier.
207 |
208 | ```json
209 | {
210 | "type": "Text",
211 | "title": "Title",
212 | "modifiers": {
213 | "shadow": {
214 | "color": "black",
215 | "radius": 5,
216 | "x": 0,
217 | "y": 2
218 | }
219 | }
220 | }
221 | ```
222 |
223 | ## Opacity
224 |
225 | You can change the opacity of a component using the `opacity` modifier.
226 |
227 | ```json
228 | [
229 | {
230 | "type": "Text",
231 | "title": "Title",
232 | "modifiers": {
233 | "opacity": 0.5
234 | }
235 | }
236 | ]
237 | ```
238 |
239 | ## Disabled modifier
240 |
241 | You can disable user interaction for a component using the `disabled` modifier.
242 |
243 | ```json
244 | {
245 | "type": "Button",
246 | "title": "Submit",
247 | "modifiers": {
248 | "disabled": true
249 | }
250 | }
--------------------------------------------------------------------------------
/Sources/DynamicUI/Helpers/AnyCodable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Binding.onChange.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 16/04/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import Foundation
12 |
13 | /// Any Codable supports different `Codable` types as `String`, `Int`, `Data`, `Double`, `Bool`,
14 | /// and nested dictionaries `[String: AnyCodable]`.
15 | /// This is made so you can use `AnyCodable?` in a codable struct so you can use dynamic types.
16 | ///
17 | /// Example:
18 | /// ```swift
19 | /// struct WithAnyCodable: Codable, Hashable {
20 | /// let someOptionalString: String?
21 | /// let someOptionalCodable: AnyCodable?
22 | /// }
23 | /// ```
24 | public enum AnyCodable {
25 | /// String value
26 | case string(String)
27 |
28 | /// Integer value
29 | case int(Int)
30 |
31 | /// Data value
32 | case data(Data)
33 |
34 | /// Double value
35 | case double(Double)
36 |
37 | /// Boolean value
38 | case bool(Bool)
39 |
40 | /// Dictionary value
41 | case dictionary([String: AnyCodable])
42 |
43 | /// No value
44 | case none
45 |
46 | /// Missing value error
47 | enum AnyCodableError: Error {
48 | /// Missing value
49 | case missingValue
50 | }
51 | }
52 |
53 | extension AnyCodable {
54 | /// Convert value to String
55 | /// - Returns: value if it is a String
56 | public func toString() -> String? {
57 | if case let .string(string) = self {
58 | return string
59 | }
60 |
61 | return nil
62 | }
63 |
64 | /// Convert value to Int
65 | /// - Returns: value if it is a Integer
66 | public func toInt() -> Int? {
67 | if case let .int(int) = self {
68 | return int
69 | }
70 |
71 | return nil
72 | }
73 |
74 | /// Convert value to Data
75 | /// - Returns: value if it is data
76 | public func toData() -> Data? {
77 | if case let .data(data) = self {
78 | return data
79 | }
80 |
81 | return nil
82 | }
83 |
84 | /// Convert value to Double
85 | /// - Returns: value if it is a double
86 | public func toDouble() -> Double? {
87 | if case let .double(double) = self {
88 | return double
89 | }
90 |
91 | return nil
92 | }
93 |
94 | /// Convert value to Bool
95 | /// - Returns: value if it is a boolean
96 | public func toBool() -> Bool? {
97 | if case let .bool(bool) = self {
98 | return bool
99 | }
100 |
101 | return nil
102 | }
103 |
104 | /// Convert value to Dictionary
105 | /// - Returns: value if it is a dictionary
106 | public func toDictionary() -> [String: AnyCodable]? {
107 | if case let .dictionary(dict) = self {
108 | return dict
109 | }
110 |
111 | return nil
112 | }
113 |
114 | /// Check if value is nil
115 | /// - Returns: nil if value is none/empty
116 | public func isNil() -> Bool {
117 | if case .none = self {
118 | return true
119 | }
120 |
121 | return false
122 | }
123 | }
124 |
125 | extension AnyCodable: Codable, Equatable, Hashable {
126 | enum CodingKeys: String, CodingKey {
127 | case string, int, data, double, bool, dictionary
128 | }
129 |
130 | /// Decode the values
131 | ///
132 | /// - Parameter decoder:
133 | public init(from decoder: Decoder) throws {
134 | // Try to decode in order of most specific/common JSON types.
135 | if let int = try? decoder.singleValueContainer().decode(Int.self) {
136 | self = .int(int)
137 | return
138 | }
139 |
140 | if let string = try? decoder.singleValueContainer().decode(String.self) {
141 | self = .string(string)
142 | return
143 | }
144 |
145 | if let data = try? decoder.singleValueContainer().decode(Data.self) {
146 | self = .data(data)
147 | return
148 | }
149 |
150 | if let double = try? decoder.singleValueContainer().decode(Double.self) {
151 | self = .double(double)
152 | return
153 | }
154 |
155 | if let bool = try? decoder.singleValueContainer().decode(Bool.self) {
156 | self = .bool(bool)
157 | return
158 | }
159 |
160 | if let dict = try? decoder.singleValueContainer().decode([String: AnyCodable].self) {
161 | self = .dictionary(dict)
162 | return
163 | }
164 |
165 | // Use `self = .none` if the value can be optional
166 | // or `throw AnyCodableError.missingValue` if it may not be optional
167 | self = .none
168 | }
169 |
170 | /// Encode the values
171 | ///
172 | /// - Parameter encoder: Encoder
173 | public func encode(to encoder: Encoder) throws {
174 | var container = encoder.container(keyedBy: CodingKeys.self)
175 |
176 | switch self {
177 | case .string(let value):
178 | try container.encode(value, forKey: .string)
179 |
180 | case .int(let value):
181 | try container.encode(value, forKey: .int)
182 |
183 | case .data(let value):
184 | try container.encode(value, forKey: .data)
185 |
186 | case .double(let value):
187 | try container.encode(value, forKey: .double)
188 |
189 | case .bool(let value):
190 | try container.encode(value, forKey: .bool)
191 |
192 | case .dictionary(let value):
193 | try container.encode(value, forKey: .dictionary)
194 |
195 | case .none:
196 | _ = ""
197 | }
198 | }
199 |
200 | public func hash(into hasher: inout Hasher) {
201 | switch self {
202 | case .string(let value):
203 | hasher.combine(0)
204 | hasher.combine(value)
205 |
206 | case .int(let value):
207 | hasher.combine(1)
208 | hasher.combine(value)
209 |
210 | case .data(let value):
211 | hasher.combine(2)
212 | hasher.combine(value)
213 |
214 | case .double(let value):
215 | hasher.combine(3)
216 | hasher.combine(value)
217 |
218 | case .bool(let value):
219 | hasher.combine(4)
220 | hasher.combine(value)
221 |
222 | case .dictionary(let dict):
223 | hasher.combine(5)
224 | // Ensure deterministic hashing by sorting keys
225 | for key in dict.keys.sorted() {
226 | hasher.combine(key)
227 | if let value = dict[key] {
228 | hasher.combine(value)
229 | }
230 | }
231 |
232 | case .none:
233 | hasher.combine(6)
234 | }
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/Extensions/View.modifiers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Modifiers.swift
3 | // DynamicUI
4 | //
5 | // Created by Wesley de Groot on 25/07/2024.
6 | // https://wesleydegroot.nl
7 | //
8 | // https://github.com/0xWDG/DynamicUI
9 | // MIT LICENCE
10 |
11 | import SwiftUI
12 | import OSLog
13 |
14 | struct DynamicUIModifier: ViewModifier {
15 | /// The modifiers to apply
16 | let modifiers: [String: AnyCodable]?
17 |
18 | // TODO: Ideally, this function would use @ViewBuilder to avoid type erasure with AnyView,
19 | // which would improve type safety and allow for more natural SwiftUI composition.
20 | // However, applying modifiers dynamically based on a dictionary of keys and values
21 | // currently requires type erasure, since @ViewBuilder expects a static view hierarchy.
22 | // Investigate approaches to apply modifiers in a type-safe way without AnyView,
23 | // possibly by refactoring how modifiers are represented or applied.
24 | func body(content: Content) -> some View {
25 | // swiftlint:disable:previous cyclomatic_complexity function_body_length
26 | var tempView = AnyView(content)
27 |
28 | modifiers?.forEach { key, value in
29 | switch key {
30 | case "color", "foregroundStyle", "foregroundColor":
31 | guard #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *),
32 | let string = value.toString(),
33 | let color = DynamicUIHelper.translateColor(string) else { break }
34 | tempView = AnyView(tempView.foregroundStyle(color))
35 |
36 | case "background", "backgroundColor", "backgroundStyle":
37 | guard #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *),
38 | let string = value.toString(),
39 | let color = DynamicUIHelper.translateColor(string) else { break }
40 | tempView = AnyView(tempView.background(color))
41 |
42 | case "fontWeight":
43 | guard #available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *),
44 | let string = value.toString(),
45 | let weight = DynamicUIHelper.translateFontWeight(string) else { break }
46 | tempView = AnyView(tempView.fontWeight(weight))
47 |
48 | case "font":
49 | guard #available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) else { break }
50 | tempView = AnyView(tempView.font(.none))
51 |
52 | case "bold":
53 | guard #available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) else { break }
54 | tempView = AnyView(tempView.bold(value.toBool() ?? true))
55 |
56 | case "italic":
57 | guard #available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) else { break }
58 | tempView = AnyView(tempView.italic(value.toBool() ?? true))
59 |
60 | case "monospaced":
61 | guard #available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) else { break }
62 | tempView = AnyView(tempView.monospaced(value.toBool() ?? true))
63 |
64 | case "frame":
65 | guard #available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) else { break }
66 | if let frameDict = value.toDictionary() {
67 | let width = frameDict["width"]?.toDouble().map { CGFloat($0) }
68 | let height = frameDict["height"]?.toDouble().map { CGFloat($0) }
69 | let minWidth = frameDict["minWidth"]?.toDouble().map { CGFloat($0) }
70 | let idealWidth = frameDict["idealWidth"]?.toDouble().map { CGFloat($0) }
71 | let maxWidth = frameDict["maxWidth"]?.toDouble().map { CGFloat($0) }
72 | let minHeight = frameDict["minHeight"]?.toDouble().map { CGFloat($0) }
73 | let idealHeight = frameDict["idealHeight"]?.toDouble().map { CGFloat($0) }
74 | let maxHeight = frameDict["maxHeight"]?.toDouble().map { CGFloat($0) }
75 | let alignment = DynamicUIHelper.translateAlignment(frameDict["alignment"]?.toString())
76 |
77 | if width != nil || height != nil {
78 | tempView = AnyView(
79 | tempView.frame(
80 | width: width,
81 | height: height,
82 | alignment: alignment
83 | )
84 | )
85 | } else {
86 | tempView = AnyView(
87 | tempView.frame(
88 | minWidth: minWidth,
89 | idealWidth: idealWidth,
90 | maxWidth: maxWidth,
91 | minHeight: minHeight,
92 | idealHeight: idealHeight,
93 | maxHeight: maxHeight,
94 | alignment: alignment
95 | )
96 | )
97 | }
98 | }
99 |
100 | case "opacity":
101 | guard #available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *),
102 | let opacity = value.toDouble() else { break }
103 | tempView = AnyView(tempView.opacity(opacity))
104 |
105 | case "disabled":
106 | guard #available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *),
107 | let disabled = value.toBool() else { break }
108 | tempView = AnyView(tempView.disabled(disabled))
109 |
110 | case "padding":
111 | if value.toBool() != nil {
112 | tempView = AnyView(tempView.padding())
113 | } else if let padding = value.toDouble() {
114 | tempView = AnyView(tempView.padding(padding))
115 | } else {
116 | break
117 | }
118 |
119 | default:
120 | break
121 | }
122 | }
123 |
124 | return tempView
125 | }
126 | }
127 |
128 | extension View {
129 | /// DynamicUIModifiers
130 | ///
131 | /// This function adds modifiers to a DynamicUIView
132 | ///
133 | /// - Parameter modifiers: The modifiers to apply
134 | ///
135 | /// - Returns: The modified view
136 | func dynamicUIModifiers(_ modifiers: [String: AnyCodable]?) -> some View {
137 | self.modifier(DynamicUIModifier(modifiers: modifiers))
138 | }
139 |
140 | /// Set Modifiers
141 | ///
142 | /// This function sets the modifiers for a DynamicUIView
143 | ///
144 | /// - Parameter modifiers: The modifiers to set
145 | ///
146 | /// - Returns: The modified view
147 | func set(modifiers: DynamicUIComponent) -> some View {
148 | var tempView = AnyView(self)
149 | var modifiers = modifiers
150 |
151 | if let identifier = modifiers.identifier {
152 | tempView = AnyView(tempView.id(identifier))
153 | }
154 |
155 | if let disabled = modifiers.disabled {
156 | if modifiers.modifiers == nil {
157 | modifiers.modifiers = ["disabled": .bool(disabled)]
158 | } else {
159 | modifiers.modifiers?.updateValue(.bool(disabled), forKey: "disabled")
160 | }
161 | }
162 |
163 | return tempView.dynamicUIModifiers(modifiers.modifiers)
164 | }
165 | }
166 |
167 | #if DEBUG
168 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
169 | #Preview {
170 | let json = """
171 | [
172 | {
173 | "type": "Text",
174 | "title": "Title",
175 | "disabled": true,
176 | "modifiers": {
177 | "foregroundColor": "purple",
178 | "bold": true,
179 | "monospaced": true
180 | }
181 | },
182 | {
183 | "type": "Button",
184 | "title": "Button (disabled)",
185 | "disabled": true
186 | }
187 | ]
188 | """
189 |
190 | DynamicUI(json: json, component: .constant(nil))
191 | }
192 | #endif
193 |
--------------------------------------------------------------------------------
/Sources/DynamicUI/DynamicUI.swift:
--------------------------------------------------------------------------------
1 | //
2 | //
3 | // DynamicUI.swift
4 | // DynamicUI
5 | //
6 | // Created by Wesley de Groot on 16/04/2024.
7 | // https://wesleydegroot.nl
8 | //
9 | // https://github.com/0xWDG/DynamicUI
10 | // MIT LICENCE
11 |
12 | import SwiftUI
13 |
14 | /// DynamicUI
15 | ///
16 | /// DynamicUI is a SwiftUI View that can be used to display an interface based on JSON.
17 | public struct DynamicUI: View {
18 | /// DynamicUIComponent state change handler
19 | public typealias Callback = (DynamicUIComponent) -> Void
20 |
21 | /// JSON data to generate the interface from
22 | private var json: Data?
23 |
24 | /// Legacy callback support
25 | private var legacyCallback: Callback?
26 |
27 | /// Callback for interactions with the DynamicUIComponents
28 | @Binding
29 | var component: DynamicUIComponent?
30 |
31 | /// This state is used to store the error message
32 | @Binding
33 | private var error: Error?
34 |
35 | /// Internal error state
36 | @State
37 | private var internalError: Error? {
38 | didSet {
39 | error = internalError
40 | }
41 | }
42 |
43 | /// This state is used to store the layout
44 | @State
45 | private var layout: [DynamicUIComponent]?
46 |
47 | /// Initialize DynamicUI
48 | ///
49 | /// - Parameter json: JSON Data
50 | /// - Parameter component: Binding for the dynamic UI element
51 | /// - Parameter error: Error message
52 | public init(json: Data, component: Binding, error: Binding? = nil) {
53 | self.json = json
54 | self._component = component
55 | self._error = error ?? .constant(nil)
56 | }
57 |
58 | /// Initialize DynamicUI
59 | ///
60 | /// - Parameter json: JSON String
61 | /// - Parameter component: Binding for the dynamic UI element
62 | /// - Parameter error: Error message
63 | public init(json: String, component: Binding, error: Binding? = nil) {
64 | self.json = json.data(using: .utf8)
65 | self._component = component
66 | self._error = error ?? .constant(nil)
67 | }
68 |
69 | /// Initialize DynamicUI
70 | ///
71 | /// - Parameter json: JSON Data
72 | /// - Parameter callback: Callback handler for updates
73 | /// - Parameter error: Error message
74 | public init(json: Data, callback: @escaping Callback, error: Binding? = nil) {
75 | self.json = json
76 | self.legacyCallback = callback
77 | self._component = Binding(
78 | get: { nil },
79 | set: { value in
80 | if let value {
81 | callback(value)
82 | }
83 | }
84 | )
85 | self._error = error ?? .constant(nil)
86 | }
87 |
88 | /// Initialize DynamicUI
89 | ///
90 | /// - Parameter json: JSON String
91 | /// - Parameter callback: Callback handler for updates
92 | /// - Parameter error: Error message
93 | public init(json: String, callback: @escaping Callback, error: Binding? = nil) {
94 | self.json = json.data(using: .utf8)
95 | self.legacyCallback = callback
96 | self._component = Binding(
97 | get: { nil },
98 | set: { value in
99 | if let value {
100 | callback(value)
101 | }
102 | }
103 | )
104 | self._error = error ?? .constant(nil)
105 | }
106 |
107 | /// Initialize the DynamicUI
108 | public var body: some View {
109 | VStack {
110 | if let layout = layout {
111 | buildView(for: layout)
112 | } else if let error = internalError {
113 | Image(systemName: "exclamationmark.arrow.triangle.2.circlepath")
114 | .resizable()
115 | .frame(width: 150, height: 150)
116 | .padding(.vertical)
117 |
118 | Text("Failed to generate interface...")
119 | .font(.title)
120 | .padding(.vertical)
121 |
122 | #if DEBUG
123 | Text(error.localizedDescription)
124 | #endif
125 | } else {
126 | ProgressView()
127 | .frame(width: 150, height: 150)
128 | #if !os(tvOS) && !os(watchOS)
129 | .controlSize(.large)
130 | #endif
131 | .padding()
132 |
133 | Text("Generating interface...")
134 | }
135 | }
136 | .onAppear {
137 | decodeJSON()
138 | }
139 | }
140 |
141 | /// Decode the JSON data
142 | private func decodeJSON() {
143 | self.internalError = nil
144 |
145 | do {
146 | if let json = json {
147 | self.layout = try JSONDecoder().decode(
148 | [DynamicUIComponent].self,
149 | from: json
150 | )
151 | }
152 | } catch {
153 | self.internalError = error
154 | #if DEBUG
155 | print(error)
156 | #endif
157 | }
158 | }
159 |
160 | /// Build a SwiftUI View based on the components
161 | /// - Parameter components: [UIComponent]
162 | /// - Returns: A SwiftUI View
163 | func buildView(for components: [DynamicUIComponent]) -> some View {
164 | // swiftlint:disable:previous cyclomatic_complexity function_body_length
165 | return ForEach(components, id: \.self) { component in
166 | switch component.type {
167 | case "Button":
168 | DynamicButton(component)
169 | .environment(\.internalDynamicUIEnvironment, self)
170 |
171 | case "VStack":
172 | DynamicVStack(component)
173 | .environment(\.internalDynamicUIEnvironment, self)
174 |
175 | case "HStack":
176 | DynamicHStack(component)
177 | .environment(\.internalDynamicUIEnvironment, self)
178 |
179 | case "ZStack":
180 | DynamicZStack(component)
181 | .environment(\.internalDynamicUIEnvironment, self)
182 |
183 | case "List":
184 | DynamicList(component)
185 | .environment(\.internalDynamicUIEnvironment, self)
186 |
187 | case "ScrollView":
188 | DynamicScrollView(component)
189 | .environment(\.internalDynamicUIEnvironment, self)
190 |
191 | case "NavigationView":
192 | DynamicNavigationView(component)
193 | .environment(\.internalDynamicUIEnvironment, self)
194 |
195 | case "Form":
196 | DynamicForm(component)
197 | .environment(\.internalDynamicUIEnvironment, self)
198 |
199 | case "Text":
200 | DynamicText(component)
201 | .environment(\.internalDynamicUIEnvironment, self)
202 |
203 | case "Image":
204 | DynamicImage(component)
205 | .environment(\.internalDynamicUIEnvironment, self)
206 |
207 | case "Divider":
208 | Divider()
209 | .environment(\.internalDynamicUIEnvironment, self)
210 |
211 | case "Spacer":
212 | Spacer()
213 | .environment(\.internalDynamicUIEnvironment, self)
214 |
215 | case "Section":
216 | DynamicSection(component)
217 | .environment(\.internalDynamicUIEnvironment, self)
218 |
219 | case "Label":
220 | DynamicLabel(component)
221 | .environment(\.internalDynamicUIEnvironment, self)
222 |
223 | case "TextField":
224 | DynamicTextField(component)
225 | .environment(\.internalDynamicUIEnvironment, self)
226 |
227 | case "SecureField":
228 | DynamicSecureField(component)
229 | .environment(\.internalDynamicUIEnvironment, self)
230 |
231 | case "TextEditor":
232 | DynamicTextEditor(component)
233 | .environment(\.internalDynamicUIEnvironment, self)
234 |
235 | case "Toggle":
236 | DynamicToggle(component)
237 | .environment(\.internalDynamicUIEnvironment, self)
238 |
239 | case "Gauge":
240 | DynamicGauge(component)
241 | .environment(\.internalDynamicUIEnvironment, self)
242 |
243 | case "ProgressView":
244 | DynamicProgressView(component)
245 | .environment(\.internalDynamicUIEnvironment, self)
246 |
247 | case "Slider":
248 | DynamicSlider(component)
249 | .environment(\.internalDynamicUIEnvironment, self)
250 |
251 | case "GroupBox":
252 | DynamicGroupBox(component)
253 | .environment(\.internalDynamicUIEnvironment, self)
254 |
255 | case "DisclosureGroup":
256 | DynamicDisclosureGroup(component)
257 | .environment(\.internalDynamicUIEnvironment, self)
258 |
259 | case "HSplitView":
260 | DynamicHSplitView(component)
261 | .environment(\.internalDynamicUIEnvironment, self)
262 |
263 | case "VSplitView":
264 | DynamicVSplitView(component)
265 | .environment(\.internalDynamicUIEnvironment, self)
266 |
267 | case "Picker":
268 | DynamicPicker(component)
269 | .environment(\.internalDynamicUIEnvironment, self)
270 |
271 | // NavigationSplitView
272 | // TabView
273 |
274 | default:
275 | EmptyView()
276 | }
277 | }
278 | }
279 | }
280 |
281 | private struct InternalDynamicUIKey: EnvironmentKey {
282 | static let defaultValue: DynamicUI = defaultValue
283 | }
284 |
285 | extension EnvironmentValues {
286 | var internalDynamicUIEnvironment: DynamicUI {
287 | get { self[InternalDynamicUIKey.self] }
288 | set { self[InternalDynamicUIKey.self] = newValue }
289 | }
290 | }
291 |
--------------------------------------------------------------------------------
/Playground/DynamicUI Playground.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 70;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 2B2D6CD22C4990A7008E0692 /* DynamicUI_PlaygroundApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B2D6CD12C4990A7008E0692 /* DynamicUI_PlaygroundApp.swift */; };
11 | 2B2D6CD42C4990A7008E0692 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B2D6CD32C4990A7008E0692 /* ContentView.swift */; };
12 | 2B2D6CD62C4990A9008E0692 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2B2D6CD52C4990A9008E0692 /* Assets.xcassets */; };
13 | 2B2D6CD92C4990A9008E0692 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2B2D6CD82C4990A9008E0692 /* Preview Assets.xcassets */; };
14 | 2B2D6CE32C4990D7008E0692 /* DynamicUI in Frameworks */ = {isa = PBXBuildFile; productRef = 2B2D6CE22C4990D7008E0692 /* DynamicUI */; };
15 | 2BEC27212E942FEA002A29EF /* DynamicUI Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 2BEC27012E942FE9002A29EF /* DynamicUI Watch App.app */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
16 | 2BEC27312E94305F002A29EF /* DynamicUI in Frameworks */ = {isa = PBXBuildFile; productRef = 2BEC27302E94305F002A29EF /* DynamicUI */; };
17 | /* End PBXBuildFile section */
18 |
19 | /* Begin PBXContainerItemProxy section */
20 | 2BEC271F2E942FEA002A29EF /* PBXContainerItemProxy */ = {
21 | isa = PBXContainerItemProxy;
22 | containerPortal = 2B2D6CC62C4990A7008E0692 /* Project object */;
23 | proxyType = 1;
24 | remoteGlobalIDString = 2BEC27002E942FE9002A29EF;
25 | remoteInfo = "DynamicUI Watch App";
26 | };
27 | /* End PBXContainerItemProxy section */
28 |
29 | /* Begin PBXCopyFilesBuildPhase section */
30 | 2BEC27222E942FEA002A29EF /* Embed Watch Content */ = {
31 | isa = PBXCopyFilesBuildPhase;
32 | buildActionMask = 2147483647;
33 | dstPath = "$(CONTENTS_FOLDER_PATH)/Watch";
34 | dstSubfolderSpec = 16;
35 | files = (
36 | 2BEC27212E942FEA002A29EF /* DynamicUI Watch App.app in Embed Watch Content */,
37 | );
38 | name = "Embed Watch Content";
39 | runOnlyForDeploymentPostprocessing = 0;
40 | };
41 | /* End PBXCopyFilesBuildPhase section */
42 |
43 | /* Begin PBXFileReference section */
44 | 2B2D6CCE2C4990A7008E0692 /* DynamicUI Playground.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "DynamicUI Playground.app"; sourceTree = BUILT_PRODUCTS_DIR; };
45 | 2B2D6CD12C4990A7008E0692 /* DynamicUI_PlaygroundApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicUI_PlaygroundApp.swift; sourceTree = ""; };
46 | 2B2D6CD32C4990A7008E0692 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
47 | 2B2D6CD52C4990A9008E0692 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
48 | 2B2D6CD82C4990A9008E0692 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
49 | 2B2D6CDA2C4990A9008E0692 /* DynamicUI_Playground.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DynamicUI_Playground.entitlements; sourceTree = ""; };
50 | 2B2D6CE02C4990C8008E0692 /* DynamicUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = DynamicUI; path = ..; sourceTree = ""; };
51 | 2BEC27012E942FE9002A29EF /* DynamicUI Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "DynamicUI Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; };
52 | /* End PBXFileReference section */
53 |
54 | /* Begin PBXFileSystemSynchronizedRootGroup section */
55 | 2BEC27022E942FE9002A29EF /* DynamicUI Watch App */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = "DynamicUI Watch App"; sourceTree = ""; };
56 | /* End PBXFileSystemSynchronizedRootGroup section */
57 |
58 | /* Begin PBXFrameworksBuildPhase section */
59 | 2B2D6CCB2C4990A7008E0692 /* Frameworks */ = {
60 | isa = PBXFrameworksBuildPhase;
61 | buildActionMask = 2147483647;
62 | files = (
63 | 2B2D6CE32C4990D7008E0692 /* DynamicUI in Frameworks */,
64 | );
65 | runOnlyForDeploymentPostprocessing = 0;
66 | };
67 | 2BEC26FE2E942FE9002A29EF /* Frameworks */ = {
68 | isa = PBXFrameworksBuildPhase;
69 | buildActionMask = 2147483647;
70 | files = (
71 | 2BEC27312E94305F002A29EF /* DynamicUI in Frameworks */,
72 | );
73 | runOnlyForDeploymentPostprocessing = 0;
74 | };
75 | /* End PBXFrameworksBuildPhase section */
76 |
77 | /* Begin PBXGroup section */
78 | 2B2D6CC52C4990A7008E0692 = {
79 | isa = PBXGroup;
80 | children = (
81 | 2B2D6CE02C4990C8008E0692 /* DynamicUI */,
82 | 2B2D6CD02C4990A7008E0692 /* DynamicUI Playground */,
83 | 2BEC27022E942FE9002A29EF /* DynamicUI Watch App */,
84 | 2B2D6CCF2C4990A7008E0692 /* Products */,
85 | 2B2D6CE12C4990D7008E0692 /* Frameworks */,
86 | );
87 | sourceTree = "";
88 | };
89 | 2B2D6CCF2C4990A7008E0692 /* Products */ = {
90 | isa = PBXGroup;
91 | children = (
92 | 2B2D6CCE2C4990A7008E0692 /* DynamicUI Playground.app */,
93 | 2BEC27012E942FE9002A29EF /* DynamicUI Watch App.app */,
94 | );
95 | name = Products;
96 | sourceTree = "";
97 | };
98 | 2B2D6CD02C4990A7008E0692 /* DynamicUI Playground */ = {
99 | isa = PBXGroup;
100 | children = (
101 | 2B2D6CD12C4990A7008E0692 /* DynamicUI_PlaygroundApp.swift */,
102 | 2B2D6CD32C4990A7008E0692 /* ContentView.swift */,
103 | 2B2D6CD52C4990A9008E0692 /* Assets.xcassets */,
104 | 2B2D6CDA2C4990A9008E0692 /* DynamicUI_Playground.entitlements */,
105 | 2B2D6CD72C4990A9008E0692 /* Preview Content */,
106 | );
107 | path = "DynamicUI Playground";
108 | sourceTree = "";
109 | };
110 | 2B2D6CD72C4990A9008E0692 /* Preview Content */ = {
111 | isa = PBXGroup;
112 | children = (
113 | 2B2D6CD82C4990A9008E0692 /* Preview Assets.xcassets */,
114 | );
115 | path = "Preview Content";
116 | sourceTree = "";
117 | };
118 | 2B2D6CE12C4990D7008E0692 /* Frameworks */ = {
119 | isa = PBXGroup;
120 | children = (
121 | );
122 | name = Frameworks;
123 | sourceTree = "";
124 | };
125 | /* End PBXGroup section */
126 |
127 | /* Begin PBXNativeTarget section */
128 | 2B2D6CCD2C4990A7008E0692 /* DynamicUI Playground */ = {
129 | isa = PBXNativeTarget;
130 | buildConfigurationList = 2B2D6CDD2C4990A9008E0692 /* Build configuration list for PBXNativeTarget "DynamicUI Playground" */;
131 | buildPhases = (
132 | 2B2D6CCA2C4990A7008E0692 /* Sources */,
133 | 2B2D6CCB2C4990A7008E0692 /* Frameworks */,
134 | 2B2D6CCC2C4990A7008E0692 /* Resources */,
135 | 2BEC27222E942FEA002A29EF /* Embed Watch Content */,
136 | );
137 | buildRules = (
138 | );
139 | dependencies = (
140 | 2BEC27202E942FEA002A29EF /* PBXTargetDependency */,
141 | );
142 | name = "DynamicUI Playground";
143 | packageProductDependencies = (
144 | 2B2D6CE22C4990D7008E0692 /* DynamicUI */,
145 | );
146 | productName = "DynamicUI Playground";
147 | productReference = 2B2D6CCE2C4990A7008E0692 /* DynamicUI Playground.app */;
148 | productType = "com.apple.product-type.application";
149 | };
150 | 2BEC27002E942FE9002A29EF /* DynamicUI Watch App */ = {
151 | isa = PBXNativeTarget;
152 | buildConfigurationList = 2BEC27292E942FEA002A29EF /* Build configuration list for PBXNativeTarget "DynamicUI Watch App" */;
153 | buildPhases = (
154 | 2BEC26FD2E942FE9002A29EF /* Sources */,
155 | 2BEC26FE2E942FE9002A29EF /* Frameworks */,
156 | 2BEC26FF2E942FE9002A29EF /* Resources */,
157 | );
158 | buildRules = (
159 | );
160 | dependencies = (
161 | );
162 | fileSystemSynchronizedGroups = (
163 | 2BEC27022E942FE9002A29EF /* DynamicUI Watch App */,
164 | );
165 | name = "DynamicUI Watch App";
166 | packageProductDependencies = (
167 | 2BEC27302E94305F002A29EF /* DynamicUI */,
168 | );
169 | productName = "DynamicUI Watch App";
170 | productReference = 2BEC27012E942FE9002A29EF /* DynamicUI Watch App.app */;
171 | productType = "com.apple.product-type.application";
172 | };
173 | /* End PBXNativeTarget section */
174 |
175 | /* Begin PBXProject section */
176 | 2B2D6CC62C4990A7008E0692 /* Project object */ = {
177 | isa = PBXProject;
178 | attributes = {
179 | BuildIndependentTargetsInParallel = 1;
180 | LastSwiftUpdateCheck = 2600;
181 | LastUpgradeCheck = 1540;
182 | TargetAttributes = {
183 | 2B2D6CCD2C4990A7008E0692 = {
184 | CreatedOnToolsVersion = 15.4;
185 | };
186 | 2BEC27002E942FE9002A29EF = {
187 | CreatedOnToolsVersion = 26.0.1;
188 | };
189 | };
190 | };
191 | buildConfigurationList = 2B2D6CC92C4990A7008E0692 /* Build configuration list for PBXProject "DynamicUI Playground" */;
192 | compatibilityVersion = "Xcode 14.0";
193 | developmentRegion = en;
194 | hasScannedForEncodings = 0;
195 | knownRegions = (
196 | en,
197 | Base,
198 | );
199 | mainGroup = 2B2D6CC52C4990A7008E0692;
200 | productRefGroup = 2B2D6CCF2C4990A7008E0692 /* Products */;
201 | projectDirPath = "";
202 | projectRoot = "";
203 | targets = (
204 | 2B2D6CCD2C4990A7008E0692 /* DynamicUI Playground */,
205 | 2BEC27002E942FE9002A29EF /* DynamicUI Watch App */,
206 | );
207 | };
208 | /* End PBXProject section */
209 |
210 | /* Begin PBXResourcesBuildPhase section */
211 | 2B2D6CCC2C4990A7008E0692 /* Resources */ = {
212 | isa = PBXResourcesBuildPhase;
213 | buildActionMask = 2147483647;
214 | files = (
215 | 2B2D6CD92C4990A9008E0692 /* Preview Assets.xcassets in Resources */,
216 | 2B2D6CD62C4990A9008E0692 /* Assets.xcassets in Resources */,
217 | );
218 | runOnlyForDeploymentPostprocessing = 0;
219 | };
220 | 2BEC26FF2E942FE9002A29EF /* Resources */ = {
221 | isa = PBXResourcesBuildPhase;
222 | buildActionMask = 2147483647;
223 | files = (
224 | );
225 | runOnlyForDeploymentPostprocessing = 0;
226 | };
227 | /* End PBXResourcesBuildPhase section */
228 |
229 | /* Begin PBXSourcesBuildPhase section */
230 | 2B2D6CCA2C4990A7008E0692 /* Sources */ = {
231 | isa = PBXSourcesBuildPhase;
232 | buildActionMask = 2147483647;
233 | files = (
234 | 2B2D6CD42C4990A7008E0692 /* ContentView.swift in Sources */,
235 | 2B2D6CD22C4990A7008E0692 /* DynamicUI_PlaygroundApp.swift in Sources */,
236 | );
237 | runOnlyForDeploymentPostprocessing = 0;
238 | };
239 | 2BEC26FD2E942FE9002A29EF /* Sources */ = {
240 | isa = PBXSourcesBuildPhase;
241 | buildActionMask = 2147483647;
242 | files = (
243 | );
244 | runOnlyForDeploymentPostprocessing = 0;
245 | };
246 | /* End PBXSourcesBuildPhase section */
247 |
248 | /* Begin PBXTargetDependency section */
249 | 2BEC27202E942FEA002A29EF /* PBXTargetDependency */ = {
250 | isa = PBXTargetDependency;
251 | platformFilter = ios;
252 | target = 2BEC27002E942FE9002A29EF /* DynamicUI Watch App */;
253 | targetProxy = 2BEC271F2E942FEA002A29EF /* PBXContainerItemProxy */;
254 | };
255 | /* End PBXTargetDependency section */
256 |
257 | /* Begin XCBuildConfiguration section */
258 | 2B2D6CDB2C4990A9008E0692 /* Debug */ = {
259 | isa = XCBuildConfiguration;
260 | buildSettings = {
261 | ALWAYS_SEARCH_USER_PATHS = NO;
262 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
263 | CLANG_ANALYZER_NONNULL = YES;
264 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
265 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
266 | CLANG_ENABLE_MODULES = YES;
267 | CLANG_ENABLE_OBJC_ARC = YES;
268 | CLANG_ENABLE_OBJC_WEAK = YES;
269 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
270 | CLANG_WARN_BOOL_CONVERSION = YES;
271 | CLANG_WARN_COMMA = YES;
272 | CLANG_WARN_CONSTANT_CONVERSION = YES;
273 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
274 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
275 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
276 | CLANG_WARN_EMPTY_BODY = YES;
277 | CLANG_WARN_ENUM_CONVERSION = YES;
278 | CLANG_WARN_INFINITE_RECURSION = YES;
279 | CLANG_WARN_INT_CONVERSION = YES;
280 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
281 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
282 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
283 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
284 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
285 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
286 | CLANG_WARN_STRICT_PROTOTYPES = YES;
287 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
288 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
289 | CLANG_WARN_UNREACHABLE_CODE = YES;
290 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
291 | COPY_PHASE_STRIP = NO;
292 | DEBUG_INFORMATION_FORMAT = dwarf;
293 | ENABLE_STRICT_OBJC_MSGSEND = YES;
294 | ENABLE_TESTABILITY = YES;
295 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
296 | GCC_C_LANGUAGE_STANDARD = gnu17;
297 | GCC_DYNAMIC_NO_PIC = NO;
298 | GCC_NO_COMMON_BLOCKS = YES;
299 | GCC_OPTIMIZATION_LEVEL = 0;
300 | GCC_PREPROCESSOR_DEFINITIONS = (
301 | "DEBUG=1",
302 | "$(inherited)",
303 | );
304 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
305 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
306 | GCC_WARN_UNDECLARED_SELECTOR = YES;
307 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
308 | GCC_WARN_UNUSED_FUNCTION = YES;
309 | GCC_WARN_UNUSED_VARIABLE = YES;
310 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
311 | MACOSX_DEPLOYMENT_TARGET = 14.5;
312 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
313 | MTL_FAST_MATH = YES;
314 | ONLY_ACTIVE_ARCH = YES;
315 | SDKROOT = macosx;
316 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
317 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
318 | };
319 | name = Debug;
320 | };
321 | 2B2D6CDC2C4990A9008E0692 /* Release */ = {
322 | isa = XCBuildConfiguration;
323 | buildSettings = {
324 | ALWAYS_SEARCH_USER_PATHS = NO;
325 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
326 | CLANG_ANALYZER_NONNULL = YES;
327 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
328 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
329 | CLANG_ENABLE_MODULES = YES;
330 | CLANG_ENABLE_OBJC_ARC = YES;
331 | CLANG_ENABLE_OBJC_WEAK = YES;
332 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
333 | CLANG_WARN_BOOL_CONVERSION = YES;
334 | CLANG_WARN_COMMA = YES;
335 | CLANG_WARN_CONSTANT_CONVERSION = YES;
336 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
337 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
338 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
339 | CLANG_WARN_EMPTY_BODY = YES;
340 | CLANG_WARN_ENUM_CONVERSION = YES;
341 | CLANG_WARN_INFINITE_RECURSION = YES;
342 | CLANG_WARN_INT_CONVERSION = YES;
343 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
344 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
345 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
346 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
347 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
348 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
349 | CLANG_WARN_STRICT_PROTOTYPES = YES;
350 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
351 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
352 | CLANG_WARN_UNREACHABLE_CODE = YES;
353 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
354 | COPY_PHASE_STRIP = NO;
355 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
356 | ENABLE_NS_ASSERTIONS = NO;
357 | ENABLE_STRICT_OBJC_MSGSEND = YES;
358 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
359 | GCC_C_LANGUAGE_STANDARD = gnu17;
360 | GCC_NO_COMMON_BLOCKS = YES;
361 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
362 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
363 | GCC_WARN_UNDECLARED_SELECTOR = YES;
364 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
365 | GCC_WARN_UNUSED_FUNCTION = YES;
366 | GCC_WARN_UNUSED_VARIABLE = YES;
367 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
368 | MACOSX_DEPLOYMENT_TARGET = 14.5;
369 | MTL_ENABLE_DEBUG_INFO = NO;
370 | MTL_FAST_MATH = YES;
371 | SDKROOT = macosx;
372 | SWIFT_COMPILATION_MODE = wholemodule;
373 | };
374 | name = Release;
375 | };
376 | 2B2D6CDE2C4990A9008E0692 /* Debug */ = {
377 | isa = XCBuildConfiguration;
378 | buildSettings = {
379 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
380 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
381 | CODE_SIGN_ENTITLEMENTS = "DynamicUI Playground/DynamicUI_Playground.entitlements";
382 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
383 | CODE_SIGN_STYLE = Automatic;
384 | COMBINE_HIDPI_IMAGES = YES;
385 | CURRENT_PROJECT_VERSION = 1;
386 | DEVELOPMENT_ASSET_PATHS = "\"DynamicUI Playground/Preview Content\"";
387 | DEVELOPMENT_TEAM = 4V2D72S45C;
388 | ENABLE_PREVIEWS = YES;
389 | GENERATE_INFOPLIST_FILE = YES;
390 | INFOPLIST_KEY_NSHumanReadableCopyright = "";
391 | INFOPLIST_KEY_UILaunchStoryboardName = Launchscreen;
392 | INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
393 | LD_RUNPATH_SEARCH_PATHS = (
394 | "$(inherited)",
395 | "@executable_path/../Frameworks",
396 | );
397 | MARKETING_VERSION = 1.0;
398 | PRODUCT_BUNDLE_IDENTIFIER = "nl.wesleydegroot.DynamicUI-Playground";
399 | PRODUCT_NAME = "$(TARGET_NAME)";
400 | SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator";
401 | SUPPORTS_MACCATALYST = NO;
402 | SWIFT_EMIT_LOC_STRINGS = YES;
403 | SWIFT_VERSION = 5.0;
404 | TARGETED_DEVICE_FAMILY = "1,2,3,7";
405 | };
406 | name = Debug;
407 | };
408 | 2B2D6CDF2C4990A9008E0692 /* Release */ = {
409 | isa = XCBuildConfiguration;
410 | buildSettings = {
411 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
412 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
413 | CODE_SIGN_ENTITLEMENTS = "DynamicUI Playground/DynamicUI_Playground.entitlements";
414 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
415 | CODE_SIGN_STYLE = Automatic;
416 | COMBINE_HIDPI_IMAGES = YES;
417 | CURRENT_PROJECT_VERSION = 1;
418 | DEVELOPMENT_ASSET_PATHS = "\"DynamicUI Playground/Preview Content\"";
419 | DEVELOPMENT_TEAM = 4V2D72S45C;
420 | ENABLE_PREVIEWS = YES;
421 | GENERATE_INFOPLIST_FILE = YES;
422 | INFOPLIST_KEY_NSHumanReadableCopyright = "";
423 | INFOPLIST_KEY_UILaunchStoryboardName = Launchscreen;
424 | INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
425 | LD_RUNPATH_SEARCH_PATHS = (
426 | "$(inherited)",
427 | "@executable_path/../Frameworks",
428 | );
429 | MARKETING_VERSION = 1.0;
430 | PRODUCT_BUNDLE_IDENTIFIER = "nl.wesleydegroot.DynamicUI-Playground";
431 | PRODUCT_NAME = "$(TARGET_NAME)";
432 | SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator";
433 | SUPPORTS_MACCATALYST = NO;
434 | SWIFT_EMIT_LOC_STRINGS = YES;
435 | SWIFT_VERSION = 5.0;
436 | TARGETED_DEVICE_FAMILY = "1,2,3,7";
437 | };
438 | name = Release;
439 | };
440 | 2BEC27232E942FEA002A29EF /* Debug */ = {
441 | isa = XCBuildConfiguration;
442 | buildSettings = {
443 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
444 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
445 | CODE_SIGN_STYLE = Automatic;
446 | CURRENT_PROJECT_VERSION = 1;
447 | DEVELOPMENT_TEAM = 4V2D72S45C;
448 | ENABLE_PREVIEWS = YES;
449 | GENERATE_INFOPLIST_FILE = YES;
450 | INFOPLIST_FILE = "DynamicUI-Watch-App-Info.plist";
451 | INFOPLIST_KEY_CFBundleDisplayName = DynamicUI;
452 | INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
453 | INFOPLIST_KEY_WKCompanionAppBundleIdentifier = "nl.wesleydegroot.DynamicUI-Playground";
454 | LD_RUNPATH_SEARCH_PATHS = (
455 | "$(inherited)",
456 | "@executable_path/Frameworks",
457 | );
458 | MARKETING_VERSION = 1.0;
459 | PRODUCT_BUNDLE_IDENTIFIER = "nl.wesleydegroot.DynamicUI-Playground.watchkitapp";
460 | PRODUCT_NAME = "$(TARGET_NAME)";
461 | SDKROOT = watchos;
462 | SKIP_INSTALL = YES;
463 | STRING_CATALOG_GENERATE_SYMBOLS = YES;
464 | SWIFT_APPROACHABLE_CONCURRENCY = YES;
465 | SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
466 | SWIFT_EMIT_LOC_STRINGS = YES;
467 | SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
468 | SWIFT_VERSION = 5.0;
469 | TARGETED_DEVICE_FAMILY = 4;
470 | WATCHOS_DEPLOYMENT_TARGET = 26.0;
471 | };
472 | name = Debug;
473 | };
474 | 2BEC27242E942FEA002A29EF /* Release */ = {
475 | isa = XCBuildConfiguration;
476 | buildSettings = {
477 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
478 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
479 | CODE_SIGN_STYLE = Automatic;
480 | CURRENT_PROJECT_VERSION = 1;
481 | DEVELOPMENT_TEAM = 4V2D72S45C;
482 | ENABLE_PREVIEWS = YES;
483 | GENERATE_INFOPLIST_FILE = YES;
484 | INFOPLIST_FILE = "DynamicUI-Watch-App-Info.plist";
485 | INFOPLIST_KEY_CFBundleDisplayName = DynamicUI;
486 | INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
487 | INFOPLIST_KEY_WKCompanionAppBundleIdentifier = "nl.wesleydegroot.DynamicUI-Playground";
488 | LD_RUNPATH_SEARCH_PATHS = (
489 | "$(inherited)",
490 | "@executable_path/Frameworks",
491 | );
492 | MARKETING_VERSION = 1.0;
493 | PRODUCT_BUNDLE_IDENTIFIER = "nl.wesleydegroot.DynamicUI-Playground.watchkitapp";
494 | PRODUCT_NAME = "$(TARGET_NAME)";
495 | SDKROOT = watchos;
496 | SKIP_INSTALL = YES;
497 | STRING_CATALOG_GENERATE_SYMBOLS = YES;
498 | SWIFT_APPROACHABLE_CONCURRENCY = YES;
499 | SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
500 | SWIFT_EMIT_LOC_STRINGS = YES;
501 | SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
502 | SWIFT_VERSION = 5.0;
503 | TARGETED_DEVICE_FAMILY = 4;
504 | VALIDATE_PRODUCT = YES;
505 | WATCHOS_DEPLOYMENT_TARGET = 26.0;
506 | };
507 | name = Release;
508 | };
509 | /* End XCBuildConfiguration section */
510 |
511 | /* Begin XCConfigurationList section */
512 | 2B2D6CC92C4990A7008E0692 /* Build configuration list for PBXProject "DynamicUI Playground" */ = {
513 | isa = XCConfigurationList;
514 | buildConfigurations = (
515 | 2B2D6CDB2C4990A9008E0692 /* Debug */,
516 | 2B2D6CDC2C4990A9008E0692 /* Release */,
517 | );
518 | defaultConfigurationIsVisible = 0;
519 | defaultConfigurationName = Release;
520 | };
521 | 2B2D6CDD2C4990A9008E0692 /* Build configuration list for PBXNativeTarget "DynamicUI Playground" */ = {
522 | isa = XCConfigurationList;
523 | buildConfigurations = (
524 | 2B2D6CDE2C4990A9008E0692 /* Debug */,
525 | 2B2D6CDF2C4990A9008E0692 /* Release */,
526 | );
527 | defaultConfigurationIsVisible = 0;
528 | defaultConfigurationName = Release;
529 | };
530 | 2BEC27292E942FEA002A29EF /* Build configuration list for PBXNativeTarget "DynamicUI Watch App" */ = {
531 | isa = XCConfigurationList;
532 | buildConfigurations = (
533 | 2BEC27232E942FEA002A29EF /* Debug */,
534 | 2BEC27242E942FEA002A29EF /* Release */,
535 | );
536 | defaultConfigurationIsVisible = 0;
537 | defaultConfigurationName = Release;
538 | };
539 | /* End XCConfigurationList section */
540 |
541 | /* Begin XCSwiftPackageProductDependency section */
542 | 2B2D6CE22C4990D7008E0692 /* DynamicUI */ = {
543 | isa = XCSwiftPackageProductDependency;
544 | productName = DynamicUI;
545 | };
546 | 2BEC27302E94305F002A29EF /* DynamicUI */ = {
547 | isa = XCSwiftPackageProductDependency;
548 | productName = DynamicUI;
549 | };
550 | /* End XCSwiftPackageProductDependency section */
551 | };
552 | rootObject = 2B2D6CC62C4990A7008E0692 /* Project object */;
553 | }
554 |
--------------------------------------------------------------------------------