- Overview
- Key Concepts
- Getting Started
- Usage Examples
- Hints File Format
- Layout Hints and Section Grouping (NEW in v5.0.0+)
- Display Width System
- Field Hints Properties
- Storage and Organization
- Caching and Performance
- Testing
- Migration Guide
- Best Practices
- Troubleshooting
The Field Hints System in SixLayer Framework v5.0.0 allows you to declaratively describe how your data models should be presented. Instead of manually configuring each field in code, you define hints once in .hints files and 6Layer automatically applies them everywhere your data is presented.
Data Persistence Support: Field hints work seamlessly with both CoreData AND/OR Swift Data. The hints describe your data models, not the persistence layer, so you can use the same hints whether your models are CoreData entities or Swift Data models.
Hints describe the DATA, not the view.
This means hints are tied to your data models, not to specific views. Define how User data should look once, and it automatically applies to:
- Create user forms
- Edit user forms
- User detail views
- User list views
- Any other view that presents User data
Instead of:
TextField("Username", text: $username)
.frame(width: 200) // Manual configurationYou write:
{
"username": {
"displayWidth": "medium"
}
}- Define hints once in a
.hintsfile - Use everywhere that data model is presented
- Cached for performance
- Hints describe the data structure
- Not tied to specific views
- Reusable across all presentation contexts
// Models/User.swift
struct User: Identifiable {
let id: UUID
let username: String
let email: String
let bio: String?
let postalCode: String
}Create Hints/User.hints:
Note: After creating the hints file, you need to add it to your Xcode project:
- Via Xcode: Drag the
.hintsfile into your project navigator and ensure it's included in your app target - Via xcodegen: Add the hints file to your
project.ymlresources section - Via Package.swift: For Swift Package projects, include hints in your package resources
{
"username": {
"displayWidth": "medium",
"expectedLength": 20,
"maxLength": 50,
"minLength": 3
},
"email": {
"displayWidth": "wide",
"maxLength": 255
},
"bio": {
"displayWidth": "wide",
"showCharacterCounter": true,
"maxLength": 1000
},
"postalCode": {
"displayWidth": "narrow",
"maxLength": 10
}
}let fields = createUserFields()
let view = platformPresentFormData_L1(
fields: fields,
hints: EnhancedPresentationHints(
dataType: .form,
presentationPreference: .form,
context: .create
),
modelName: "User" // 6Layer reads User.hints automatically!
)struct CreateUserView: View {
let fields = [
DynamicFormField(
id: "username",
contentType: .text,
label: "Username",
isRequired: true
),
DynamicFormField(
id: "email",
contentType: .email,
label: "Email",
isRequired: true
),
DynamicFormField(
id: "bio",
contentType: .textarea,
label: "Biography"
)
]
var body: some View {
platformPresentFormData_L1(
fields: fields,
hints: EnhancedPresentationHints(
dataType: .form,
context: .create
),
modelName: "User"
)
}
}// Hints/User.hints
{
"username": { "displayWidth": "medium" }
}
// Hints/Product.hints
{
"name": { "displayWidth": "wide" },
"price": { "displayWidth": "narrow" }
}
// In your views
platformPresentFormData_L1(..., modelName: "User")
platformPresentFormData_L1(..., modelName: "Product"){
"fieldName": {
"displayWidth": "medium",
"expectedLength": 20,
"maxLength": 50,
"minLength": 3,
"showCharacterCounter": "true"
}
}All properties in hints files are strings:
- Numeric:
"expectedLength": "20"(parsed as Int) - Boolean:
"showCharacterCounter": "true"(parsed as Bool) - String:
"displayWidth": "medium"(stored as String)
JSON doesn't support comments, but you can document in a separate file:
{
"_comment": "User.hints - Describes how to present User data",
"username": {
"displayWidth": "medium"
}
}Layout hints extend the field-level hints system to include structural organization - defining how groups of fields should be displayed together and what layout style to use for each group.
Key principle: Layout hints describe data relationships - which fields belong together and in what order. They're hints, not commandments - the framework adapts layouts responsively based on available space and platform capabilities.
Add a _sections array to your .hints file to define field groupings:
User.hints:
{
"username": {
"displayWidth": "medium",
"expectedLength": 20
},
"email": {
"displayWidth": "wide"
},
"bio": {
"displayWidth": "wide",
"showCharacterCounter": true
},
"postalCode": {
"displayWidth": "narrow"
},
"_sections": [
{
"id": "basic-info",
"title": "Basic Information",
"description": "Enter your account details",
"fields": ["username", "email"],
"layoutStyle": "vertical"
},
{
"id": "personal-info",
"title": "Personal Details",
"fields": ["bio", "postalCode"],
"layoutStyle": "horizontal"
}
]
}Each section in _sections supports:
| Property | Required | Type | Description |
|---|---|---|---|
id |
Yes | String | Unique identifier for the section |
title |
Yes | String | Section title (used for accessibility) |
description |
No | String | Optional section description text |
fields |
No | Array[String] | Field IDs that belong to this section, in display order |
layoutStyle |
No | String | Layout strategy (see below) |
isCollapsible |
No | Boolean | Whether section can be collapsed/expanded (default: false) |
isCollapsed |
No | Boolean | Initial collapsed state (default: false, i.e., expanded) |
Sections can be made collapsible by setting isCollapsible: true. Use isCollapsed to control the initial state:
{
"_sections": [
{
"id": "basic-info",
"title": "Basic Information",
"fields": ["username", "email"],
"isCollapsible": true,
"isCollapsed": false
},
{
"id": "advanced",
"title": "Advanced Options",
"fields": ["bio", "preferences"],
"isCollapsible": true,
"isCollapsed": true
}
]
}isCollapsible: true- Enables collapse/expand UI (DisclosureGroup)isCollapsed: false- Section starts expanded (default)isCollapsed: true- Section starts collapsed
The layoutStyle property supports the following values. All are hints - the framework adapts based on available space:
vertical(default): Fields stacked verticallyhorizontal: Fields displayed side-by-side (2 columns)grid: Adaptive grid layout based on field countadaptive: Framework chooses layout based on field count:- ≤4 fields: vertical
- 5-8 fields: horizontal (2 columns)
-
8 fields: grid
standard,compact,spacious: Vertical layouts with different spacing
Layout resolution follows this precedence (highest to lowest):
- Explicit LayoutSpec: If you pass a
LayoutSpectoplatformPresentFormData_L1, it overrides hints - Hints file
_sections: Sections defined in.hintsfile - Framework defaults: Single default section with all fields
User.hints:
{
"username": {
"displayWidth": "medium",
"expectedLength": 20
},
"email": {
"displayWidth": "wide"
},
"bio": {
"displayWidth": "wide",
"showCharacterCounter": true
},
"phone": {
"displayWidth": "medium"
},
"address": {
"displayWidth": "wide"
},
"postalCode": {
"displayWidth": "narrow"
},
"_sections": [
{
"id": "account",
"title": "Account Information",
"description": "Your login credentials",
"fields": ["username", "email"],
"layoutStyle": "vertical"
},
{
"id": "contact",
"title": "Contact Information",
"fields": ["phone", "address", "postalCode"],
"layoutStyle": "horizontal"
},
{
"id": "profile",
"title": "Profile",
"fields": ["bio"],
"layoutStyle": "vertical"
}
]
}Usage in Swift:
let fields = [
DynamicFormField(id: "username", contentType: .text, label: "Username"),
DynamicFormField(id: "email", contentType: .email, label: "Email"),
DynamicFormField(id: "phone", contentType: .telephoneNumber, label: "Phone"),
DynamicFormField(id: "address", contentType: .text, label: "Address"),
DynamicFormField(id: "postalCode", textContentType: .postalCode, label: "Postal Code"),
DynamicFormField(id: "bio", contentType: .textarea, label: "Biography")
]
platformPresentFormData_L1(
fields: fields,
hints: EnhancedPresentationHints(
dataType: .form,
context: .create
),
modelName: "User" // Loads User.hints with _sections automatically!
)For special cases where you need to override hints programmatically:
let customLayout = LayoutSpec(sections: [
DynamicFormSection(
id: "custom-section",
title: "Custom Layout",
fields: [fields[0], fields[1]],
layoutStyle: .grid
)
])
platformPresentFormData_L1(
fields: fields,
hints: EnhancedPresentationHints(...),
modelName: "User",
layoutSpec: customLayout // Overrides hints file sections
)If a section references a field ID that doesn't exist in your form fields:
- A warning is logged to the console:
⚠️ Warning: Section '...' references fields that don't exist: ... - The missing field is ignored
- The section is created with the remaining valid fields
This provides graceful degradation - your hints file can reference fields that aren't always present.
Fields within a section are displayed in the order specified in the fields array in your hints file. This gives you full control over field ordering within each section.
- Data-Driven Layout: Layout structure defined with your data, not scattered in code
- DRY: Define layout once in hints, use everywhere
- Responsive: Framework adapts layouts based on available space
- Accessible: Section titles used for accessibility identifiers
- Flexible: Can override programmatically with
LayoutSpecwhen needed - Backward Compatible: Existing hints files without
_sectionscontinue to work
| Width | Points | Use Cases |
|---|---|---|
narrow |
~150 | Postal codes, phone extensions, short codes |
medium |
~200 | Usernames, cities, short names |
wide |
~400 | Full names, emails, addresses, descriptions |
Specify exact width in points:
{
"customField": {
"displayWidth": "250"
}
}- narrow: Fixed-format fields (zip codes, codes)
- medium: Standard input fields
- wide: Long text, addresses, descriptions
- numeric: Custom width requirements
public struct FieldDisplayHints: Sendable {
public let expectedLength: Int?
public let displayWidth: String?
public let showCharacterCounter: Bool
public let maxLength: Int?
public let minLength: Int?
public let metadata: [String: String]
}- Type: Int
- Purpose: Expected maximum length for display sizing
- Example:
"expectedLength": "20" - Use: Helps determine field width
- Type: String
- Purpose: Visual width of the field
- Values:
"narrow","medium","wide", or numeric - Example:
"displayWidth": "medium"
- Type: Bool (stored as
"true"/"false") - Purpose: Show character count overlay
- Example:
"showCharacterCounter": "true"
- Type: Int
- Purpose: Maximum allowed length for validation
- Example:
"maxLength": "50"
- Type: Int
- Purpose: Minimum required length for validation
- Example:
"minLength": "3"
- Type: Dictionary<String, String>
- Purpose: Additional custom metadata
- Example: Any extra properties
YourApp/
├── Models/
│ ├── User.swift
│ ├── Product.swift
│ └── Order.swift
├── Hints/
│ ├── User.hints ← All hints in one folder
│ ├── Product.hints
│ └── Order.hints
└── Views/
├── CreateUserView.swift
└── EditUserView.swift
- Hints file:
{ModelName}.hints - Model name must match exactly
- Case-sensitive
- App Bundle (primary):
Hints/User.hints - Documents Directory (runtime):
~/Documents/Hints/User.hints - Root Level (backward compat):
User.hints
6Layer checks in this order and uses the first file found.
// First call: loads from file
let hints1 = loadHintsFromFile(for: "User")
// Second call: returns cached
let hints2 = loadHintsFromFile(for: "User")
// hints1 === hints2 (same reference)- Load Time: File I/O only on first access
- Memory: Cached for lifetime of app
- Scalability: Performance doesn't degrade with more hints
- Thread Safety: Cached on MainActor
Cache is managed automatically. To clear (if needed):
// In a real implementation, would provide API for this
hintsCache.clearCache(for: "User")# All field hints tests
swift test --filter FieldDisplayHintsTests
swift test --filter FieldHintsLoaderTests
swift test --filter FieldHintsDRYTests
swift test --filter FieldHintsIntegrationTests
# All at once
swift test --filter FieldHints✅ Basic functionality
✅ Hints loading from files
✅ Caching behavior
✅ Multiple models
✅ Hints merging
✅ Integration workflow
Field hints are opt-in. Your existing code continues to work:
// Still works, no changes needed
platformPresentFormData_L1(
fields: fields,
hints: EnhancedPresentationHints(...)
)- Create
Hints/folder - Add
{ModelName}.hintsfiles - Pass
modelNameparameter
// Before: no hints
platformPresentFormData_L1(
fields: fields,
hints: EnhancedPresentationHints(...)
)
// After: with hints (optional!)
platformPresentFormData_L1(
fields: fields,
hints: EnhancedPresentationHints(...),
modelName: "User" // ← Add this
)User.swift → User.hints
Product.swift → Product.hints
Order.swift → Order.hints
Keep all hints together:
Hints/
User.hints
Product.hints
Order.hints
{
"zipCode": { "displayWidth": "narrow" }, // ✓
"email": { "displayWidth": "wide" }, // ✓
"bio": { "displayWidth": "wide" } // ✓
}{
"username": {
"minLength": "3",
"maxLength": "50" // Reasonable limits
}
}{
"description": {
"displayWidth": "wide",
"maxLength": "500",
"showCharacterCounter": "true" // Helpful for users
}
}Problem: Hints not applied to views
Solutions:
- Check file exists:
Hints/{ModelName}.hints - Verify naming matches exactly (case-sensitive)
- Ensure file is added to app target in Xcode
- Check JSON syntax is valid
Problem: Changes to hints file not reflected
Solution: This is expected behavior (DRY). To see changes:
- Restart app
- Or implement cache clearing (future enhancement)
Problem: Hints file has syntax errors
Solutions:
- Validate JSON syntax
- Check all strings quoted properly
- Ensure numeric values as strings:
"20"not20
- Field Hints Guide - Quick start guide
- Hints DRY Architecture - DRY principles
- Hints Folder Structure - File organization
- AutoLoad Hints Example - Complete example
- Release Notes v5.0.0 - Release notes
The Field Hints System makes UI generation more declarative, maintainable, and DRY:
✅ Define hints once in .hints files
✅ Use everywhere automatically
✅ Organized in Hints/ folder
✅ Cached for performance
✅ Fully backward compatible
✅ Type-safe and tested
Start using hints today!