A server-driven dynamic form engine built with SwiftUI using MVVM + lightweight Clean Architecture.
The application dynamically renders form UI from local JSON configuration and supports validation, reusable components, theming, rich text links, and scalable form state management while keeping the architecture practical and maintainable.
- Dynamic form rendering from local JSON
- MVVM + lightweight Clean Architecture
- DTO → Domain mapping
- Dynamic field validation
- Reusable form components
- Theme support using EnvironmentValues
- Rich text terms & conditions component using AttributedString
- Multi-select, dropdown, checkbox, text, email and number fields
- URL handling with in-app Safari support
- Unit testing for decoding, validation and ViewModel logic
My overall approach was to keep UI, business logic, and data handling separated into clear layers for better maintainability, readability, and testing.
I used:
- SwiftUI
- MVVM
- Lightweight Clean Architecture
I intentionally avoided overengineering the project with unnecessary layers, networking abstractions, or heavy dependency injection frameworks since the task uses local JSON.
The architecture flow is:
Local JSON
↓
DTO Decoding
↓
DTO → Domain Mapping
↓
Use Case
↓
ViewModel
↓
SwiftUI Rendering
EulerityDynamicForm
│
├── App
│
├── Core
│ ├── Extensions
│ └── Utilities
│
├── Data
│ ├── DTOs
│ ├── Mappers
│ └── Sources
│
├── Domain
│ ├── Entities
│ ├── Validation
│ └── UseCases
│
├── Presentation
│ ├── Components
│ ├── ViewModels
│ ├── Views
│ └── Theme
│
└── Resources
I separated DTOs from Domain models instead of directly using decoded JSON models throughout the app.
This allowed:
- safer type conversion
- cleaner business models
- fallback handling
- isolation from backend/schema changes
Example:
JSON String Type → Domain Enum Type
I used:
[String: FormValue]instead of:
[String: String]because the form contains multiple field types such as:
- text
- checkbox
- dropdown
- multi-select
This made state management more scalable and type-safe.
The form theme is injected using EnvironmentValues so deeply nested components can access styling configuration without prop drilling.
If an unknown field type comes from JSON, the app does not crash.
Instead:
- the field is skipped safely
- no broken UI is rendered
I chose this because dynamic/server-driven UI should degrade gracefully instead of crashing on unsupported schema values.
Validation is not aggressively triggered while the user types the first character.
Instead:
- validation happens on submit
- errors are cleared as user updates values
This creates a cleaner and less frustrating UX.
For text fields with max length:
- I used
String.prefix - typing is prevented beyond allowed length
I also added a safety check:
maxLength > 0before applying the restriction.
This prevents accidental blocking when max length is missing or invalid.
I implemented a reusable rich text component using AttributedString.
The component accepts:
message: String
metadata: [String: String]Where:
- key = clickable text
- value = URL
I used custom URL handling to support opening links either:
- inside an in-app Safari view
- or externally if needed
Validation is handled separately from UI rendering through a dedicated validator layer.
Supported validation includes:
- required text fields
- email validation
- checkbox validation
- multi-select validation
- numeric input filtering
The project includes unit tests for:
- JSON loading & decoding
- DTO → Domain mapping
- Validation logic
- ViewModel state updates
- Form submission logic
I focused testing on business logic and edge cases instead of snapshot/UI-heavy testing.
If I had more time, I would improve:
- accessibility support
- better keyboard navigation handling
- stronger form submission abstraction
- field-level async validation support
- improved animation polish
- snapshot/UI testing coverage
- dynamic section/group support
- more advanced schema-driven validation rules
One issue I encountered was loading local JSON correctly during unit testing.
Bundle.main does not work reliably inside test targets because tests run in a separate bundle context.
I solved this by:
- injecting
Bundleinto the data source - using
Bundle(for:)inside tests - adding JSON resources to the test target membership
This made decoding logic properly testable.
Initially, I considered using:
[String: String]for form state.
However, this quickly became limiting because the form included:
- checkboxes
- dropdowns
- multi-select fields
I redesigned the state using:
[String: FormValue]with associated enum values, which made the solution much more scalable and type-safe.
- SwiftUI
- MVVM
- Clean Architecture
- XCTest / Swift Testing
- AttributedString
- EnvironmentValues
- SFSafariViewController