Advanced Features - Tai-Kimura/SwiftJsonUI GitHub Wiki
Advanced Features in SwiftJsonUI
This document covers advanced features and techniques in SwiftJsonUI for building sophisticated, dynamic user interfaces.
Table of Contents
- Shadow Configuration
- Custom Binding Scripts
- Touch Handling and Gestures
- Dynamic Image Loading
- Advanced Layout Techniques
- Collection View Configuration
- Complex Event Handling
Shadow Configuration
Shadow Object Structure
The shadow attribute accepts both string and object formats for advanced shadow effects:
Simple Shadow (String)
{
"type": "View",
"shadow": "#000000"
}
Advanced Shadow (Object)
{
"type": "View",
"shadow": {
"color": "#000000",
"offsetX": 0,
"offsetY": 2,
"opacity": 0.3,
"radius": 4
}
}
Shadow Properties
| Property | Type | Description | Default |
|---|---|---|---|
color |
string | Shadow color (hex) | #000000 |
offsetX |
float | Horizontal offset | 0 |
offsetY |
float | Vertical offset | 2 |
opacity |
float | Shadow opacity (0.0-1.0) | 0.2 |
radius |
float | Blur radius | 3 |
Advanced Shadow Examples
Card with Elevation
{
"type": "View",
"background": "#FFFFFF",
"cornerRadius": 8,
"shadow": {
"color": "#000000",
"offsetY": 4,
"opacity": 0.15,
"radius": 8
}
}
Dynamic Shadow
{
"type": "View",
"shadow": {
"color": "@{isSelected ? #007AFF : #000000}",
"opacity": "@{isSelected ? 0.5 : 0.2}",
"offsetY": "@{isPressed ? 1 : 4}",
"radius": "@{isPressed ? 2 : 6}"
}
}
Custom Binding Scripts
Overview
bindingScript allows direct Swift code execution for complex binding logic that can't be expressed with simple expressions.
Basic Usage
{
"type": "View",
"id": "custom_view",
"bindingScript": "customView.alpha = isVisible ? 1.0 : 0.5"
}
Multi-line Scripts
{
"type": "Label",
"id": "status_label",
"bindingScript": "let color: UIColor\nswitch status {\ncase .active:\n color = .green\ncase .pending:\n color = .orange\ncase .inactive:\n color = .red\ndefault:\n color = .gray\n}\nstatusLabel.textColor = color\nstatusLabel.text = status.description"
}
Accessing Multiple Views
{
"bindingScript": "if let button = self.viewWithTag(100) as? UIButton {\n button.isEnabled = formIsValid\n}\nif let label = self.viewWithTag(101) as? UILabel {\n label.text = formIsValid ? 'Ready' : 'Complete all fields'\n}"
}
Animation in Binding Scripts
{
"bindingScript": "UIView.animate(withDuration: 0.3) {\n customView.alpha = isVisible ? 1.0 : 0.0\n customView.transform = isExpanded ? .identity : CGAffineTransform(scaleX: 0.8, y: 0.8)\n}"
}
Touch Handling and Gestures
Touch Disabled States
Control touch propagation with touchDisabledState:
{
"type": "View",
"touchDisabledState": "viewsWithoutTouchEnabled",
"touchEnabledViewIds": ["button1", "button2"],
"child": [
{
"type": "Button",
"id": "button1",
"text": "Enabled"
},
{
"type": "Button",
"id": "button2",
"text": "Also Enabled"
},
{
"type": "Button",
"id": "button3",
"text": "Disabled by parent"
}
]
}
Touch Disabled State Options
| Value | Description |
|---|---|
none |
Normal touch handling |
onlyMe |
Disable touch only for this view |
viewsWithoutTouchEnabled |
Disable all except specified views |
viewsWithoutInList |
Disable views not in the enabled list |
Can Tap Property
Enable tap handling for non-button views:
{
"type": "View",
"canTap": true,
"onclick": "handleViewTap",
"tapBackground": "#F0F0F0"
}
Complex Event Handling
Define multiple event types with the events dictionary:
{
"type": "View",
"events": {
"onclick": "handleTap",
"onlongtap": "handleLongPress",
"pan": {
"selector": "handlePan:",
"minimumDistance": 10,
"maximumDistance": 100
},
"swipe": {
"selector": "handleSwipe:",
"direction": "left"
},
"rotation": {
"selector": "handleRotation:"
}
}
}
Dynamic Image Loading
Source Name Binding
Use srcName for dynamic image selection:
{
"type": "Image",
"srcName": "@{iconType + '_icon'}",
"contentMode": "AspectFit"
}
Conditional Image Sources
{
"type": "Image",
"src": "@{isSelected ? 'checked' : 'unchecked'}",
"highlightSrc": "@{isSelected ? 'checked_highlight' : 'unchecked_highlight'}"
}
Network Image with URL Binding
{
"type": "NetworkImage",
"url": "@{user.profileImageUrl}",
"defaultImage": "default_avatar",
"errorImage": "error_image",
"loadingImage": "loading_spinner"
}
Advanced Layout Techniques
Direction-based Layouts
Control layout flow direction:
{
"type": "View",
"orientation": "vertical",
"direction": "bottomToTop",
"child": [
{
"type": "Label",
"text": "This appears at bottom"
},
{
"type": "Label",
"text": "This appears at top"
}
]
}
Direction Options
| Value | Description |
|---|---|
topToBottom |
Default vertical flow |
bottomToTop |
Reverse vertical flow |
leftToRight |
Default horizontal flow |
rightToLeft |
Reverse horizontal flow (RTL) |
none |
No automatic layout |
Individual Padding Control
Fine-grained padding control:
{
"type": "View",
"leftPadding": 20,
"rightPadding": 20,
"topPadding": 10,
"bottomPadding": 30
}
Container Insets for Text
Control text container insets:
{
"type": "TextView",
"containerInset": [8, 12, 8, 12],
"text": "Text with custom insets"
}
Collection View Configuration
Advanced Cell Configuration
{
"type": "Collection",
"cellClasses": [
{
"className": "HeaderCell"
},
{
"className": "ContentCell"
},
{
"className": "FooterCell"
}
],
"headerClasses": [
{
"className": "SectionHeader"
}
],
"footerClasses": [
{
"className": "SectionFooter"
}
],
"insetHorizontal": 16,
"insetVertical": 8,
"horizontalScroll": true,
"setTargetAsDelegate": true,
"setTargetAsDataSource": true
}
Note: cellClasses, headerClasses, and footerClasses only accept className property. The actual cell layouts are defined separately and registered with these class names.
Dynamic Cell Class Names
{
"type": "Collection",
"cellClasses": [
{
"className": "@{cellType}Cell"
}
]
}
This allows dynamic cell type selection based on data.
Complex Event Handling
onclick vs onClick - Different Systems
onclick (Selector-based)
onclick calls Objective-C selectors or ViewModel methods:
UIKit Mode:
{
"type": "Button",
"onclick": "handleButtonTap" // Calls @objc func handleButtonTap()
}
To receive the sender as parameter, add colon:
{
"type": "Button",
"onclick": "handleButtonTap:" // Calls @objc func handleButtonTap(_ sender: UIButton)
}
SwiftUI Mode:
{
"type": "Button",
"onclick": "handleButtonTap" // Calls viewModel.handleButtonTap()
}
onClick (Closure-based)
onClick uses Swift closures defined in data:
{
"data": [
{
"name": "tapHandler",
"class": "((UITapGestureRecognizer) -> Void)",
"defaultValue": "{ _ in print(\"Tapped!\") }"
}
],
"type": "View",
"onClick": "@{tapHandler}"
}
Gesture Recognizers with Closures
SwiftJsonUI supports various gesture recognizers with closure handlers:
Long Press
{
"data": [
{
"name": "longPressHandler",
"class": "((UILongPressGestureRecognizer) -> Void)",
"defaultValue": "{ gesture in\n if gesture.state == .began {\n print(\"Long press started\")\n }\n}"
}
],
"type": "View",
"onLongPress": "@{longPressHandler}"
}
Pinch Gesture
{
"data": [
{
"name": "pinchHandler",
"class": "((UIPinchGestureRecognizer) -> Void)",
"defaultValue": "{ gesture in\n let scale = gesture.scale\n print(\"Pinch scale: \\(scale)\")\n}"
}
],
"type": "View",
"onPinch": "@{pinchHandler}"
}
Pan Gesture
{
"data": [
{
"name": "panHandler",
"class": "((UIPanGestureRecognizer) -> Void)",
"defaultValue": "{ gesture in\n let translation = gesture.translation(in: gesture.view)\n print(\"Pan: \\(translation)\")\n}"
}
],
"type": "View",
"onPan": "@{panHandler}"
}
Multiple Event Types on Buttons
For buttons with multiple touch events:
{
"type": "Button",
"onclick": [
{
"event": "touchDown",
"selector": "buttonPressed:"
},
{
"event": "touchUpInside",
"selector": "buttonReleased:"
},
{
"event": "touchDragExit",
"selector": "buttonDraggedOut:"
}
]
}
Performance Optimization
Binding Groups for Batch Updates
{
"binding_group": "form_validation",
"child": [
{
"type": "TextField",
"binding_group": "form_validation",
"text": "@{email}"
},
{
"type": "Label",
"binding_group": "form_validation",
"text": "@{emailError}",
"visibility": "@{emailError != nil ? visible : gone}"
}
]
}
Lazy Loading with Includes
{
"type": "View",
"child": [
{
"type": "View",
"visibility": "@{shouldLoadContent ? visible : gone}",
"child": [
{
"include": "@{shouldLoadContent ? 'heavy_content.json' : 'placeholder.json'}"
}
]
}
]
}
Advanced SafeArea Configuration
Partial SafeArea Insets
{
"type": "SafeAreaView",
"safeAreaInsetPositions": ["top", "bottom"],
"child": [
{
"type": "View",
"background": "#007AFF",
"child": [
{
"type": "Label",
"text": "Content with top and bottom safe area only"
}
]
}
]
}
Vertical SafeArea Shorthand
{
"type": "SafeAreaView",
"safeAreaInsetPositions": ["vertical"], // Applies to top and bottom
"child": [...]
}
Debugging Techniques
Using Tags for View Identification
{
"type": "View",
"tag": 100,
"bindingScript": "if let view = self.viewWithTag(100) {\n print('Found view: \\(view)')\n}"
}
Conditional Debugging
{
"bindingScript": "#if DEBUG\nprint('Current state: \\(currentState)')\nprint('User data: \\(userData)')\n#endif"
}
Complex Examples
Animated Expandable Card
{
"data": [
{
"name": "isExpanded",
"class": "Bool",
"defaultValue": false
}
],
"type": "View",
"id": "card",
"background": "#FFFFFF",
"cornerRadius": 12,
"shadow": {
"color": "#000000",
"offsetY": "@{isExpanded ? 8 : 2}",
"opacity": "@{isExpanded ? 0.2 : 0.1}",
"radius": "@{isExpanded ? 12 : 4}"
},
"canTap": true,
"onclick": "toggleExpanded",
"bindingScript": "UIView.animate(withDuration: 0.3) {\n card.transform = self.isExpanded ? CGAffineTransform(scaleX: 1.05, y: 1.05) : .identity\n}",
"child": [
{
"type": "Label",
"text": "Tap to expand"
},
{
"type": "View",
"height": "@{isExpanded ? 200 : 0}",
"clipToBounds": true,
"child": [
{
"type": "Label",
"text": "Expanded content here"
}
]
}
]
}
Dynamic Form with Validation
{
"data": [
{
"name": "formData",
"class": "FormModel"
},
{
"name": "errors",
"class": "Dictionary"
}
],
"type": "View",
"binding_group": "form",
"bindingScript": "let isValid = formData.email.contains('@') && formData.password.count >= 8\nsubmitButton.isEnabled = isValid\nsubmitButton.alpha = isValid ? 1.0 : 0.5",
"child": [
{
"type": "TextField",
"id": "emailField",
"binding_group": "form",
"text": "@{formData.email}",
"borderColor": "@{errors['email'] != nil ? #FF0000 : #CCCCCC}"
},
{
"type": "Label",
"binding_group": "form",
"text": "@{errors['email']}",
"fontColor": "#FF0000",
"visibility": "@{errors['email'] != nil ? visible : gone}"
},
{
"type": "Button",
"id": "submitButton",
"binding_group": "form",
"text": "Submit"
}
]
}
Best Practices
- Use binding groups for related UI updates
- Prefer expressions over bindingScript when possible
- Cache complex calculations in data models
- Test on various devices for SafeArea behavior
- Use meaningful IDs and tags for debugging
- Document complex bindingScripts with comments
- Avoid deep nesting in shadow configurations
- Optimize images for dynamic loading
See Also
- Data-Binding - Data binding fundamentals
- Include-System - Component composition
- All-Attributes - Complete attribute reference