Data Binding - Tai-Kimura/SwiftJsonUI GitHub Wiki
Data Binding in SwiftJsonUI
Data binding in SwiftJsonUI provides powerful reactive UI capabilities, allowing views to automatically update when data changes.
Table of Contents
- Basic Data Binding
- Data Declaration
- Binding Expressions
- Binding Groups
- Custom Binding Scripts
- Include System Variables
Basic Data Binding
Simple Property Binding
Use the @{} syntax to bind view properties to data variables:
{
"type": "Label",
"text": "@{userName}",
"visibility": "@{isLoggedIn ? visible : gone}"
}
Data Declaration
Define data models using the data array at the root level:
{
"data": [
{
"name": "userName",
"class": "String",
"defaultValue": "\"Guest\""
},
{
"name": "isLoggedIn",
"class": "Bool",
"defaultValue": "false"
},
{
"name": "itemCount",
"class": "Int",
"defaultValue": "0"
},
{
"name": "user",
"class": "User"
}
],
"child": [...]
}
Data Properties
| Property | Type | Description | Required |
|---|---|---|---|
name |
string | Variable name | Yes |
class |
string | Swift type (String, Bool, Int, Float, custom classes) | Yes |
defaultValue |
string | Swift code for initial value (see details below) | No |
Understanding defaultValue
The defaultValue property must be a string containing valid Swift code that will be written directly into the generated Swift file. This is NOT a JSON value.
Examples by Type:
| Data Type | JSON Definition | Generated Swift Code |
|---|---|---|
| String | "defaultValue": "\"Hello\"" |
var name: String = "Hello" |
| Empty String | "defaultValue": "\"\"" |
var name: String = "" |
| Bool | "defaultValue": "true" |
var flag: Bool = true |
| Int | "defaultValue": "42" |
var count: Int = 42 |
| Float | "defaultValue": "3.14" |
var value: Float = 3.14 |
| Array | "defaultValue": "[1, 2, 3]" |
var items: Array = [1, 2, 3] |
| Dictionary | "defaultValue": "[\"key\": \"value\"]" |
var dict: Dictionary = ["key": "value"] |
| Optional | "defaultValue": "nil" |
var user: User? = nil |
| Custom Object | "defaultValue": "User(name: \"John\")" |
var user: User = User(name: "John") |
Important Notes:
- String values require escaped quotes:
"\"text\"" - The content is written as-is into Swift code
- Must be valid Swift syntax, not JSON
- Can include Swift expressions:
"Date()" - Can call initializers:
"CGRect(x: 0, y: 0, width: 100, height: 50)"
Supported Data Types
- Primitives:
String,Bool,Int,Float,Double - Collections:
Array,Dictionary,Set - Custom Classes: Any Swift class name
- Optionals: Indicated by
?in expressions - Foundation Types:
Date,URL,Data, etc.
Binding Expressions
Expression Syntax
SwiftJsonUI supports Swift-like expressions within @{}:
Optional Chaining
{
"text": "@{user?.fullName}",
"visibility": "@{profile?.isPublic ? visible : gone}"
}
Ternary Operators
{
"background": "@{isSelected ? #007AFF : #FFFFFF}",
"text": "@{count > 0 ? '\(count) items' : 'No items'}"
}
Method Calls
{
"text": "@{message.localized()}",
"fontColor": "@{theme.textColor()}"
}
Complex Expressions
{
"visibility": "@{items.count > 0 && isEnabled ? visible : gone}",
"text": "@{String(format: '%.2f', price)}",
"enabled": "@{user.isVerified && !isLoading}"
}
String Interpolation
{
"text": "@{'Welcome, ' + userName}",
"hint": "@{'\(remainingCount) characters remaining'}"
}
Common Binding Patterns
Visibility Control
{
"visibility": "@{hasData ? visible : gone}",
"hidden": "@{!isReady}"
}
Conditional Styling
{
"background": "@{isError ? #FF0000 : #FFFFFF}",
"fontColor": "@{isDisabled ? #999999 : #000000}"
}
Dynamic Text
{
"text": "@{isLoading ? 'Loading...' : resultText}",
"hint": "@{fieldType == 'email' ? 'Enter email' : 'Enter text'}"
}
Binding Groups
Group related bindings for coordinated updates:
{
"data": [
{
"name": "formData",
"class": "FormModel"
}
],
"child": [
{
"type": "View",
"binding_group": "form_fields",
"child": [
{
"type": "TextField",
"id": "email_field",
"text": "@{formData.email}",
"binding_group": "form_fields"
},
{
"type": "TextField",
"id": "password_field",
"text": "@{formData.password}",
"binding_group": "form_fields"
},
{
"type": "Button",
"text": "Submit",
"enabled": "@{formData.isValid}",
"binding_group": "form_fields"
}
]
}
]
}
Benefits of Binding Groups
- Performance: Batch updates for multiple related views
- Consistency: Ensure related UI elements update together
- Organization: Logical grouping of related bindings
Custom Binding Scripts
For complex binding logic, use bindingScript:
{
"type": "View",
"id": "custom_view",
"bindingScript": "customView.backgroundColor = user.isPremium ? .systemBlue : .systemGray\ncustomView.layer.borderWidth = user.isPremium ? 2.0 : 0.0",
"child": [...]
}
BindingScript Features
- Direct Swift code execution
- Access to view properties via view ID
- Complex calculations and conditionals
- Multiple property updates
Example: Complex State Management
{
"type": "Label",
"id": "status_label",
"bindingScript": "switch orderStatus {\ncase .pending:\n statusLabel.text = 'Processing...'\n statusLabel.textColor = .orange\ncase .completed:\n statusLabel.text = 'Completed'\n statusLabel.textColor = .green\ncase .failed:\n statusLabel.text = 'Failed'\n statusLabel.textColor = .red\ndefault:\n statusLabel.text = 'Unknown'\n statusLabel.textColor = .gray\n}"
}
Include System Variables
Passing Data to Includes
Use shared_data to pass data to included components:
{
"type": "View",
"child": [
{
"include": "user_card.json",
"binding_id": "user_card_1",
"shared_data": {
"user": "@{currentUser}",
"showDetails": true
}
}
]
}
Receiving Data in Includes
In the included file (user_card.json):
{
"variables": [
{
"name": "user",
"class": "User"
},
{
"name": "showDetails",
"class": "Bool",
"defaultValue": false
}
],
"type": "View",
"child": [
{
"type": "Label",
"text": "@{user.name}"
},
{
"type": "Label",
"text": "@{user.email}",
"visibility": "@{showDetails ? visible : gone}"
}
]
}
Variable Properties
| Property | Type | Description | Required |
|---|---|---|---|
name |
string | Variable name | Yes |
class |
string | Variable type | Yes |
defaultValue |
any | Default value if not provided | No |
Advanced Binding Techniques
Two-Way Binding
For input fields, binding automatically works both ways:
{
"type": "TextField",
"text": "@{userInput}",
"onTextChange": "updateModel"
}
Collection Binding
Bind to array elements:
{
"text": "@{items[selectedIndex].title}",
"visibility": "@{items.count > selectedIndex ? visible : gone}"
}
Computed Properties
Use expressions for computed values:
{
"text": "@{firstName + ' ' + lastName}",
"hint": "@{'Total: $' + String(format: '%.2f', price * quantity)}"
}
Null Safety
Handle optionals safely:
{
"text": "@{user?.name ?? 'Anonymous'}",
"visibility": "@{data != nil ? visible : gone}"
}
Best Practices
- Use Binding Groups for related UI elements
- Leverage Optional Chaining for null safety
- Keep Expressions Simple - move complex logic to bindingScript
- Use Variables for reusable components
- Provide Default Values for better initialization
- Test Edge Cases with nil/empty data
Performance Considerations
- Binding groups improve performance by batching updates
- Complex expressions are evaluated on every change
- Use bindingScript for heavy computations
- Minimize nested optional chaining
Troubleshooting
Common Issues
- Binding not updating: Check binding_group assignment
- Nil crashes: Use optional chaining (
?.) - Type mismatches: Ensure data class matches usage
- Performance issues: Simplify expressions or use bindingScript
Debug Tips
- Log binding values in bindingScript
- Use simple test data first
- Verify data declaration syntax
- Check for typos in variable names
Examples
Login Form
{
"data": [
{
"name": "email",
"class": "String",
"defaultValue": "\"\""
},
{
"name": "password",
"class": "String",
"defaultValue": "\"\""
},
{
"name": "isValid",
"class": "Bool",
"defaultValue": "false"
}
],
"child": [
{
"type": "TextField",
"hint": "Email",
"text": "@{email}",
"input": "email"
},
{
"type": "TextField",
"hint": "Password",
"text": "@{password}",
"input": "password",
"secure": true
},
{
"type": "Button",
"text": "Login",
"enabled": "@{email.count > 0 && password.count >= 8}"
}
]
}
Dynamic List Item
{
"variables": [
{
"name": "item",
"class": "Product"
},
{
"name": "isInCart",
"class": "Bool"
}
],
"type": "View",
"child": [
{
"type": "Label",
"text": "@{item.name}"
},
{
"type": "Label",
"text": "@{'$' + String(item.price)}",
"fontColor": "@{item.onSale ? #FF0000 : #000000}"
},
{
"type": "Button",
"text": "@{isInCart ? 'Remove' : 'Add to Cart'}",
"background": "@{isInCart ? #FF0000 : #007AFF}"
}
]
}
See Also
- Include-System - Component composition with variables
- All-Attributes - Complete attribute reference
- Advanced-Features - Custom scripts and advanced techniques