├── .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://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2F0xWDG%2FDynamicUI%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/0xWDG/DynamicUI) [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2F0xWDG%2FDynamicUI%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/0xWDG/DynamicUI) 6 | [![Swift Package Manager](https://img.shields.io/badge/SPM-compatible-brightgreen.svg)](https://swift.org/package-manager) 7 | ![License](https://img.shields.io/github/license/0xWDG/Inspect) 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 | image 132 | 133 | ### V0.0.1 in action 134 | 135 | image 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 | --------------------------------------------------------------------------------