Include System - Tai-Kimura/SwiftJsonUI GitHub Wiki
Include System in SwiftJsonUI
The Include System enables powerful component composition and reusability in SwiftJsonUI, allowing you to build complex UIs from modular JSON components.
Table of Contents
- Basic Include Usage
- Passing Data with Variables
- Shared Data System
- Binding ID for Dynamic Updates
- Advanced Patterns
- Best Practices
Basic Include Usage
Simple Include
Include another JSON file as a component:
{
"type": "View",
"child": [
{
"include": "header.json"
},
{
"type": "Label",
"text": "Main Content"
},
{
"include": "footer.json"
}
]
}
Include with Binding ID
Assign a binding_id to the included component for unique binding context:
{
"type": "View",
"child": [
{
"include": "user_card.json",
"binding_id": "user_card_main"
}
]
}
Shared Data System
How shared_data Works
shared_data allows the parent file and included file to share the same data values. The keys in shared_data become available as data variables in the included component.
Example: Sharing User Data
In the parent file:
{
"data": [
{
"name": "currentUser",
"class": "User"
}
],
"type": "View",
"child": [
{
"include": "user_card.json",
"shared_data": {
"currentUser": "@{currentUser}"
}
}
]
}
In the included file (user_card.json):
{
"type": "View",
"background": "@{currentUser.isPremium ? #FFD700 : #FFFFFF}",
"child": [
{
"type": "Image",
"src": "@{currentUser.avatarUrl}",
"width": 60,
"height": 60
},
{
"type": "Label",
"text": "@{currentUser.name}",
"fontColor": "@{currentUser.isPremium ? #FFD700 : #000000}"
}
]
}
Using Variables for Different Data Mapping
If you want to use different variable names in the included component, use the variables system:
In the included file (user_card.json) with variables:
{
"variables": [
{
"name": "user",
"class": "User"
}
],
"type": "View",
"background": "@{user.isPremium ? #FFD700 : #FFFFFF}",
"child": [
{
"type": "Image",
"src": "@{user.avatarUrl}",
"width": 60,
"height": 60
},
{
"type": "Label",
"text": "@{user.name}",
"fontColor": "@{user.isPremium ? #FFD700 : #000000}"
}
]
}
In the parent file:
{
"data": [
{
"name": "currentUser",
"class": "User"
}
],
"type": "View",
"child": [
{
"include": "user_card.json",
"shared_data": {
"user": "@{currentUser}"
}
}
]
}
Static Shared Data
You can also pass static values:
{
"include": "button_template.json",
"shared_data": {
"buttonText": "Click Me",
"buttonColor": "#007AFF",
"isEnabled": true
}
}
Dynamic Shared Data
Pass bound data using expressions. The keys in shared_data become available in the included component:
{
"data": [
{
"name": "products",
"class": "Array"
},
{
"name": "selectedIndex",
"class": "Int",
"defaultValue": 0
},
{
"name": "user",
"class": "User"
},
{
"name": "settings",
"class": "Settings"
}
],
"child": [
{
"include": "product_detail.json",
"shared_data": {
"product": "@{products[selectedIndex]}",
"user": "@{user}",
"settings": "@{settings}"
}
}
]
}
In product_detail.json, you can directly use:
{
"type": "View",
"child": [
{
"type": "Label",
"text": "@{product.name}"
},
{
"type": "Label",
"text": "@{product.price}",
"visibility": "@{user.isLoggedIn ? visible : gone}"
},
{
"type": "Label",
"text": "@{settings.currency}"
}
]
}
Binding ID for Dynamic Updates
Purpose of binding_id
The binding_id creates a unique binding context for included components, enabling:
- Independent state management
- Dynamic updates without affecting other instances
- Multiple instances of the same component with different data
Example: Multiple Instances
{
"data": [
{
"name": "users",
"class": "Array"
}
],
"type": "View",
"child": [
{
"include": "user_row.json",
"binding_id": "user_1",
"shared_data": {
"user": "@{users[0]}"
}
},
{
"include": "user_row.json",
"binding_id": "user_2",
"shared_data": {
"user": "@{users[1]}"
}
},
{
"include": "user_row.json",
"binding_id": "user_3",
"shared_data": {
"user": "@{users[2]}"
}
}
]
}
Collection Views with Includes
For collection views, cellClasses only accepts className. Use separate cell JSON files that are registered with the collection:
{
"type": "Collection",
"cellClasses": [
{
"className": "UserCell"
},
{
"className": "HeaderCell"
}
],
"headerClasses": [
{
"className": "SectionHeader"
}
],
"footerClasses": [
{
"className": "SectionFooter"
}
]
}
The cell layouts are defined separately and registered with the collection view through the class names.
Advanced Patterns
Nested Includes
Components can include other components:
main.json:
{
"data": [
{
"name": "currentUserId",
"class": "String"
}
],
"type": "View",
"child": [
{
"include": "dashboard.json",
"shared_data": {
"currentUserId": "@{currentUserId}"
}
}
]
}
dashboard.json:
{
"type": "View",
"child": [
{
"include": "dashboard_header.json",
"shared_data": {
"title": "Dashboard",
"currentUserId": "@{currentUserId}"
}
},
{
"include": "dashboard_stats.json",
"shared_data": {
"currentUserId": "@{currentUserId}"
}
}
]
}
dashboard_header.json:
{
"type": "View",
"child": [
{
"type": "Label",
"text": "@{title}"
},
{
"type": "Label",
"text": "User ID: @{currentUserId}"
}
]
}
Conditional Includes
Use visibility to conditionally show included components:
{
"type": "View",
"child": [
{
"type": "View",
"visibility": "@{user.isPremium ? visible : gone}",
"child": [
{
"include": "premium_features.json",
"shared_data": {
"user": "@{user}"
}
}
]
},
{
"type": "View",
"visibility": "@{!user.isPremium ? visible : gone}",
"child": [
{
"include": "upgrade_prompt.json"
}
]
}
]
}
Template Pattern
Create reusable templates by sharing data:
Parent file:
{
"data": [
{
"name": "saveButton",
"class": "Dictionary",
"defaultValue": "[\"text\": \"Save\", \"icon\": \"save_icon\", \"action\": \"saveData\", \"style\": \"primary\"]"
},
{
"name": "cancelButton",
"class": "Dictionary",
"defaultValue": "[\"text\": \"Cancel\", \"action\": \"cancelAction\", \"style\": \"secondary\"]"
}
],
"type": "View",
"child": [
{
"include": "button_template.json",
"shared_data": {
"buttonData": "@{saveButton}"
}
},
{
"include": "button_template.json",
"shared_data": {
"buttonData": "@{cancelButton}"
}
}
]
}
button_template.json:
{
"type": "Button",
"text": "@{buttonData.text}",
"onclick": "@{buttonData.action}",
"style": "@{buttonData.style}",
"child": [
{
"type": "Image",
"src": "@{buttonData.icon}",
"visibility": "@{buttonData.icon != null ? visible : gone}",
"width": 20,
"height": 20
}
]
}
Form Builder Pattern
Build dynamic forms using shared data:
Parent file:
{
"data": [
{
"name": "emailField",
"class": "Dictionary",
"defaultValue": "[\"label\": \"Email\", \"placeholder\": \"Enter email\", \"type\": \"email\"]"
},
{
"name": "passwordField",
"class": "Dictionary",
"defaultValue": "[\"label\": \"Password\", \"placeholder\": \"Enter password\", \"type\": \"password\"]"
}
],
"type": "View",
"child": [
{
"include": "form_field.json",
"shared_data": {
"field": "@{emailField}"
}
},
{
"include": "form_field.json",
"shared_data": {
"field": "@{passwordField}"
}
}
]
}
form_field.json:
{
"type": "View",
"child": [
{
"type": "Label",
"text": "@{field.label}",
"visibility": "@{field.label != null ? visible : gone}"
},
{
"type": "TextField",
"hint": "@{field.placeholder}",
"input": "@{field.type}",
"visibility": "@{field.type != 'select' ? visible : gone}"
},
{
"type": "SelectBox",
"items": "@{field.options}",
"visibility": "@{field.type == 'select' ? visible : gone}"
}
]
}
File Organization
Recommended Structure
Layouts/
├── components/
│ ├── common/
│ │ ├── header.json
│ │ ├── footer.json
│ │ └── loading.json
│ ├── forms/
│ │ ├── form_field.json
│ │ ├── form_section.json
│ │ └── form_validation.json
│ └── cards/
│ ├── user_card.json
│ ├── product_card.json
│ └── info_card.json
├── screens/
│ ├── home.json
│ ├── profile.json
│ └── settings.json
└── templates/
├── button_template.json
├── list_item_template.json
└── dialog_template.json
Best Practices
1. Variable Naming
- Use descriptive variable names
- Follow consistent naming conventions
- Document required vs optional variables
2. Default Values
- Always provide sensible defaults
- Use null for optional variables
- Consider edge cases
3. Component Granularity
- Keep components focused and single-purpose
- Avoid deeply nested includes (max 3 levels)
- Balance reusability with complexity
4. Data Flow
- Pass only necessary data
- Use binding_id for stateful components
- Minimize data coupling
5. Performance
- Cache frequently used components
- Lazy load heavy components
- Use conditional includes wisely
Error Handling
Missing Variables
If a variable is not provided and has no default:
{
"variables": [
{
"name": "requiredField",
"class": "String"
// No defaultValue - will error if not provided
}
]
}
File Not Found
Handle missing includes gracefully:
{
"type": "View",
"child": [
{
"include": "optional_component.json",
"visibility": "@{fileExists ? visible : gone}"
}
]
}
Debugging Tips
- Check file paths: Ensure include paths are correct
- Verify variables: Match variable names and types
- Test defaults: Run without shared_data first
- Use unique binding_ids: Avoid conflicts
- Log shared_data: Debug data passing
Examples
Complete User Profile Component
profile_screen.json:
{
"data": [
{
"name": "user",
"class": "User"
},
{
"name": "isEditing",
"class": "Bool",
"defaultValue": false
}
],
"type": "SafeAreaView",
"child": [
{
"include": "components/profile_header.json",
"binding_id": "profile_header",
"shared_data": {
"user": "@{user}",
"showEditButton": true
}
},
{
"include": "components/profile_info.json",
"binding_id": "profile_info",
"shared_data": {
"user": "@{user}",
"isEditable": "@{isEditing}"
}
},
{
"include": "components/profile_actions.json",
"binding_id": "profile_actions",
"shared_data": {
"userId": "@{user.id}"
}
}
]
}
Dynamic Tab System
tab_container.json:
{
"data": [
{
"name": "selectedTab",
"class": "Int",
"defaultValue": 0
}
],
"type": "View",
"child": [
{
"type": "View",
"orientation": "horizontal",
"child": [
{
"include": "tab_button.json",
"shared_data": {
"title": "Home",
"index": 0,
"isSelected": "@{selectedTab == 0}"
}
},
{
"include": "tab_button.json",
"shared_data": {
"title": "Profile",
"index": 1,
"isSelected": "@{selectedTab == 1}"
}
}
]
},
{
"include": "tab_content.json",
"binding_id": "tab_content",
"shared_data": {
"tabIndex": "@{selectedTab}"
}
}
]
}
See Also
- Data-Binding - Data binding and expressions
- Advanced-Features - Advanced techniques
- All-Attributes - Complete attribute reference