Skip to content

Instantly share code, notes, and snippets.

@brennanMKE
Created July 26, 2025 02:48
Show Gist options
  • Save brennanMKE/3d08b7ee245fd87e9a1e4b4fb21c6603 to your computer and use it in GitHub Desktop.
Save brennanMKE/3d08b7ee245fd87e9a1e4b4fb21c6603 to your computer and use it in GitHub Desktop.
Server-Driven UI with SwiftUI

Server-Driven UI with SwiftUI

Yes, this is absolutely possible and is a pattern called Server-Driven UI (SDUI). It's becoming increasingly popular for mobile apps that need dynamic layouts, A/B testing, or frequent UI changes without app store updates.

Core Concept

The idea is to have your backend return JSON that describes the UI structure, components, and their properties, which your SwiftUI app then interprets and renders dynamically.

graph LR
    A[Backend API] -->|JSON Schema| B[REST Client]
    B --> C[JSON Parser]
    C --> D[UI Model]
    D --> E[SwiftUI Renderer]
    E --> F[Dynamic UI]
Loading

Implementation Approaches

1. Component-Based Architecture

Define a JSON schema that maps to SwiftUI components:

{
  "screen": {
    "title": "Product Details",
    "components": [
      {
        "type": "VStack",
        "spacing": 16,
        "children": [
          {
            "type": "Image",
            "url": "https://example.com/product.jpg",
            "aspectRatio": "fit"
          },
          {
            "type": "Text",
            "content": "Product Name",
            "style": "title"
          },
          {
            "type": "HStack",
            "children": [
              {
                "type": "Button",
                "title": "Add to Cart",
                "action": "addToCart",
                "style": "primary"
              }
            ]
          }
        ]
      }
    ]
  }
}

2. SwiftUI Implementation Strategy

Create a system with these key components:

A. UI Component Protocol

protocol UIComponent: Codable {
    var type: ComponentType { get }
    func render() -> AnyView
}

B. Component Types Enum

enum ComponentType: String, Codable {
    case vstack, hstack, text, image, button, spacer
}

C. Dynamic View Builder

struct DynamicView: View {
    let components: [UIComponent]
    
    var body: some View {
        ForEach(components.indices, id: \.self) { index in
            components[index].render()
        }
    }
}

3. Concrete Implementation Example

Here's how specific components might work:

struct TextComponent: UIComponent {
    let type: ComponentType = .text
    let content: String
    let style: TextStyle?
    
    func render() -> AnyView {
        AnyView(
            Text(content)
                .font(style?.font ?? .body)
                .foregroundColor(style?.color ?? .primary)
        )
    }
}

struct VStackComponent: UIComponent {
    let type: ComponentType = .vstack
    let spacing: CGFloat?
    let children: [AnyUIComponent]
    
    func render() -> AnyView {
        AnyView(
            VStack(spacing: spacing) {
                ForEach(children.indices, id: \.self) { index in
                    children[index].render()
                }
            }
        )
    }
}

Architecture Patterns

1. MVVM with Server-Driven UI

graph TB
    A[Server JSON] --> B[UIConfigService]
    B --> C[ScreenViewModel]
    C --> D[SwiftUI View]
    D --> E[DynamicComponentRenderer]
    E --> F[Rendered UI]
Loading

2. Repository Pattern

protocol UIConfigRepository {
    func fetchScreenConfig(for screenId: String) async throws -> ScreenConfig
}

class RemoteUIConfigRepository: UIConfigRepository {
    func fetchScreenConfig(for screenId: String) async throws -> ScreenConfig {
        // REST API call to fetch JSON
        // Parse JSON to ScreenConfig model
    }
}

Benefits

  1. Dynamic Updates: Change UI without app store releases
  2. A/B Testing: Serve different UIs to different user segments
  3. Personalization: Customize UI based on user preferences
  4. Rapid Iteration: Backend teams can modify UI independently
  5. Consistency: Ensure UI consistency across platforms

Challenges & Solutions

1. Type Safety

Challenge: JSON parsing can be error-prone Solution: Use strong typing with Codable and validation

2. Performance

Challenge: Dynamic rendering might be slower Solution: Implement caching and lazy loading

3. Complex Layouts

Challenge: Some SwiftUI layouts are hard to represent in JSON Solution: Create higher-level component abstractions

4. Actions & Navigation

Challenge: Handling user interactions dynamically Solution: Define action types and routing systems

enum ActionType: String, Codable {
    case navigate, api_call, sheet_present
}

struct ActionConfig: Codable {
    let type: ActionType
    let parameters: [String: String]
}

Advanced Features

1. Conditional Rendering

{
  "type": "ConditionalView",
  "condition": "user.isPremium",
  "trueComponent": { "type": "PremiumBanner" },
  "falseComponent": { "type": "UpgradePrompt" }
}

2. Data Binding

{
  "type": "Text",
  "content": "{{user.name}}",
  "binding": "user.name"
}

3. Styling System

{
  "type": "Button",
  "title": "Submit",
  "styleClass": "primary-button",
  "customStyles": {
    "backgroundColor": "#007AFF",
    "cornerRadius": 8
  }
}

Implementation Considerations

  1. Fallback Handling: Always have default/fallback UI for network failures
  2. Validation: Validate JSON schema on both client and server
  3. Versioning: Handle API version compatibility
  4. Security: Validate all dynamic content for security
  5. Accessibility: Ensure dynamic components support accessibility
  6. Testing: Create comprehensive tests for dynamic rendering

This approach gives you the flexibility of web-like dynamic UIs while maintaining the native performance and feel of SwiftUI. Many successful apps like Airbnb, Spotify, and Instagram use variations of this pattern for parts of their UI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment