Getting Started

Using AI to Code? Copy This Prompt

Paste this prompt into ChatGPT, Claude, Cursor, or any AI coding assistant to automatically integrate the Sequence SDK into your app.

Overview

Sequence lets you build beautiful onboarding screens in our visual editor, then show them natively in your iOS app. No design skills needed.

🎨Design onboarding visually
πŸ“±Renders natively in your app
⚑Update without app releases
πŸ“ŠBuilt-in analytics & A/B tests

Installation

1

Open Xcode Package Manager

File β†’ Add Packages

Paste this URL:

https://github.com/Musgrav/sequence-swift

Important: Use Branch, Not Version

For Dependency Rule, select Branch and type main. Do not use a version number.

Quick Start

How It Works

Splash Screen
β†’
Sequence Onboarding
β†’
Paywall
β†’
Main App
2

Get Your API Credentials

From your Sequence dashboard

Go to Settings in your dashboard to find:

appIdapiKey
3

Initialize the SDK

Add to your App's init()

MyApp.swift
import Sequence

@main
struct MyApp: App {
    init() {
        Sequence.shared.configure(
            appId: "your-app-id",
            apiKey: "your-api-key",
            baseURL: "https://screensequence.com"
        )
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
4

Show the Onboarding

Add WebViewOnboardingView to your app

ContentView.swift
import SwiftUI
import Sequence

struct ContentView: View {
    @StateObject private var sequence = Sequence.shared

    var body: some View {
        Group {
            if sequence.isOnboardingCompleted {
                // Show your paywall or main app
                MainAppView()
            } else {
                // Sequence handles everything with pixel-perfect rendering
                WebViewOnboardingView {
                    print("Onboarding finished!")
                }
            }
        }
    }
}

Use WebViewOnboardingView

Always use WebViewOnboardingView (not OnboardingView) for pixel-perfect WYSIWYG rendering that matches the editor exactly.
5

Create Your Flow in the Dashboard

Design your screens visually

Go to Flows in your dashboard, add screens, drag in blocks, and hit Publish. Your app will automatically show the new onboarding.

You're all set!

Your app will now fetch and display your onboarding flow. Changes you make in the dashboard appear instantlyβ€”no app update needed.

Core Concepts

Architecture

Sequence follows a decoupled architecture where your onboarding configuration lives on our servers and is fetched by the SDK at runtime:

How It Works
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Your Mobile    β”‚     β”‚    Sequence     β”‚     β”‚    Sequence     β”‚
β”‚      App        │────▢│      API        │◀────│   Dashboard     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        β”‚                       β”‚
        β”‚  1. Fetch config      β”‚
        │◀──────────────────────│
        β”‚                       β”‚
        β”‚  2. Render screens    β”‚
        β”‚  3. Collect data      β”‚
        β”‚  4. Track events      β”‚
        │──────────────────────▢│
        β”‚                       β”‚

Key benefits of this architecture:

  • Instant updates: Change your onboarding without releasing a new app version
  • A/B testing: The API serves different variants to different users
  • Analytics: All events flow back to the dashboard for analysis
  • Offline support: Configurations are cached locally for reliability

Screens & Flows

A Flow is a collection of Screens that users navigate through. Each screen has a type that determines its purpose:

Screen TypePurposeCommon Use Cases
welcomeFirst impression screenApp intro, value proposition
featureHighlight a featureFeature tours, benefits
permissionRequest permissionsNotifications, location, camera
carouselMulti-slide contentFeature showcase, testimonials
celebrationSuccess/completionSignup complete, achievement
nativeCustom native screenLogin, complex forms
fillerTransitional contentLoading states, progress

Screen Transitions

Configure how screens animate when navigating:

// Available transitions
type ScreenTransition =
  | 'none'           // No animation
  | 'fade'           // Opacity fade
  | 'slide-left'     // Slide from right to left
  | 'slide-right'    // Slide from left to right
  | 'slide-up'       // Slide from bottom
  | 'slide-down'     // Slide from top
  | 'fade-slide-left'  // Fade + slide left
  | 'fade-slide-right' // Fade + slide right
  | 'scale';         // Scale up from center

Content Blocks

Screens are composed of Content Blocks β€” reusable UI components that you arrange to build your screens. Sequence provides 11 block types:

BlockDescriptionData Collection
textHeadings, body text, captionsNo
imageImages with various fit modesNo
buttonInteractive buttons with presetsNo
inputText fields (email, phone, password, etc.)Yes
checklistSingle/multi-select optionsYes
sliderNumeric range selectionYes
progressProgress indicators (bar, dots, ring)No
spacerVertical spacingNo
dividerHorizontal line separatorNo
iconIcons and emojisNo
feature-cardCard with headline and bodyNo

Block Styling

Every block supports universal styling options including padding, margin, colors, shadows, border radius, and animations. Configure these in the visual editor.

Data Collection

Input blocks (input, checklist, slider) collect user data during onboarding. Each block has a unique id that becomes the key in the collected data object:

Example: Collected Data
// When onboarding completes, you receive:
{
  "user_name": "John Doe",           // From input block with id="user_name"
  "interests": ["fitness", "music"], // From checklist block with id="interests"
  "experience_level": 3,             // From slider block with id="experience_level"
}

// Handle in your onComplete callback:
<OnboardingModal
  onComplete={(collectedData) => {
    // Save to your backend
    saveUserPreferences(collectedData);

    // Or use locally
    if (collectedData.interests?.includes('fitness')) {
      showFitnessContent();
    }
  }}
/>

Validation

Configure validation rules to ensure users provide required information:

  • Input blocks: Mark as required, set input types for format validation
  • Checklist blocks: Set minimum/maximum selections
  • Navigation: Users cannot proceed until validation passes

Migration Guide

This guide walks you through replacing your existing onboarding implementation with Sequence. Whether you have a simple tutorial or a complex multi-screen flow, we'll help you migrate smoothly.

Before You Start

Audit Your Current Onboarding

Document your existing onboarding to plan the migration:

  • How many screens do you have?
  • What data do you collect from users?
  • What actions happen on each screen (permissions, API calls)?
  • How do you track completion status?
  • Do you have any A/B tests running?

Prepare Your Sequence Dashboard

  • Create your app in the Sequence dashboard
  • Copy your appId and apiKey from Settings
  • Create a new Flow that mirrors your existing screens

Feature Parity

Ensure your Sequence flow replicates all critical functionality before removing your old onboarding. Use the preview feature to test thoroughly.

Step-by-Step Migration

Step 1: Install the SDK

Add Sequence alongside your existing onboarding (don't remove anything yet):

In Xcode, go to File β†’ Add Packages and enter:

Package URL
https://github.com/Musgrav/sequence-swift

Step 2: Initialize the SDK

Configure Sequence without changing existing functionality:

MyApp.swift
import Sequence

@main
struct MyApp: App {
    init() {
        Sequence.shared.configure(
            appId: "your-app-id",
            apiKey: "your-api-key"
        )
    }

    var body: some Scene {
        WindowGroup {
            // Your existing app code - unchanged
            ExistingAppWithOldOnboarding()
        }
    }
}

Step 3: Create a Feature Flag

Use a feature flag to gradually roll out the new onboarding:

OnboardingType.swift
import Foundation

enum OnboardingType: String {
    case legacy
    case sequence
}

class OnboardingTypeManager: ObservableObject {
    @Published var type: OnboardingType = .legacy

    init() {
        checkOnboardingType()
    }

    private func checkOnboardingType() {
        // Check if user should see new onboarding
        // Options: remote config, random assignment, user cohort, etc.
        if let assigned = UserDefaults.standard.string(forKey: "onboarding_type"),
           let onboardingType = OnboardingType(rawValue: assigned) {
            self.type = onboardingType
            return
        }

        // Random 50/50 assignment for new users
        let newType: OnboardingType = Bool.random() ? .sequence : .legacy
        UserDefaults.standard.set(newType.rawValue, forKey: "onboarding_type")
        self.type = newType
    }
}

Step 4: Implement Side-by-Side

Show either the old or new onboarding based on the flag:

OnboardingGate.swift
import SwiftUI
import Sequence

struct OnboardingGate<Content: View>: View {
    @StateObject private var typeManager = OnboardingTypeManager()
    @StateObject private var sequence = Sequence.shared
    @State private var showLegacy = true
    let content: () -> Content

    var body: some View {
        Group {
            if typeManager.type == .legacy && showLegacy {
                LegacyOnboarding { data in
                    handleOnboardingData(data)
                    showLegacy = false
                }
            } else if typeManager.type == .sequence && !sequence.isOnboardingCompleted {
                WebViewOnboardingView {
                    // Onboarding completed
                }
            } else {
                content()
            }
        }
    }

    private func handleOnboardingData(_ data: [String: Any]) {
        // Your existing data handling logic
        // Works the same for both onboarding types
        saveUserPreferences(data)
        trackOnboardingComplete(data)
    }
}

Step 5: Handle Custom Actions

If your onboarding has custom screens (login, permissions), use the delegate pattern:

// Set up the delegate to handle custom actions
Sequence.shared.delegate = self

// Implement the delegate methods
extension YourViewController: SequenceDelegate {
    func sequence(_ sequence: Sequence, didRequestNativeScreen screenId: String, data: [String: Any]) -> Bool {
        if screenId == "login-screen" {
            // Show your existing login UI
            presentLoginScreen {
                // Continue onboarding after login
                Sequence.shared.track(.screenCompleted, screenId: screenId)
            }
            return true // Indicates you're handling this screen
        }
        return false
    }

    func sequence(_ sequence: Sequence, didTriggerCustomAction action: String, screenId: String) {
        switch action {
        case "request-notifications":
            requestNotificationPermission()
        case "connect-social":
            showSocialConnect()
        default:
            break
        }
    }
}

Step 6: Monitor and Iterate

Compare metrics between old and new onboarding:

  • Completion rates (visible in Sequence Analytics)
  • Time to complete
  • Drop-off points
  • User feedback

Step 7: Remove Legacy Code

Once you're confident in the new onboarding, remove the feature flag and legacy implementation:

// Final clean implementation
@main
struct MyApp: App {
    init() {
        Sequence.shared.configure(
            appId: "your-app-id",
            apiKey: "your-api-key"
        )
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

struct ContentView: View {
    @StateObject private var sequence = Sequence.shared

    var body: some View {
        Group {
            if sequence.isOnboardingCompleted {
                MainAppView()
            } else {
                WebViewOnboardingView {
                    // Onboarding completed
                }
            }
        }
    }
}

Data Mapping

Map your existing data collection to Sequence block IDs:

Your Current FieldSequence Block TypeBlock ID
Name inputinputuser_name
Email input[object Object] (email type)user_email
Interest selectionchecklistinterests
Experience slidersliderexperience_level

Keep Block IDs Consistent

Use the same block IDs as your existing field names. This way, your data handling code works without modification.

Testing Your Migration

Pre-Launch Checklist

  • All screens render correctly on iOS and Android
  • Navigation between screens works as expected
  • All data is collected and passed to onComplete
  • Custom actions (permissions, login) function properly
  • Analytics events are appearing in the dashboard
  • Offline behavior works (cached config loads)
  • Completion state persists across app restarts

Testing Commands

Reset onboarding state for testing
import Sequence

// Clear completion status to re-show onboarding
Sequence.shared.reset()

// Or reset just the onboarding state
Sequence.shared.resetOnboarding()

SDK Reference

Complete API reference for the Sequence Swift SDK. All classes and methods are available from the Sequence module.

import Sequence

// Core singleton
Sequence.shared

// Configuration
Sequence.shared.configure(appId: String, apiKey: String, baseURL: String?)

// User identification
Sequence.shared.identify(userId: String)
Sequence.shared.reset()

// Event tracking
Sequence.shared.track(_ eventType: EventType, screenId: String?, properties: [String: Any]?)

// Onboarding state
Sequence.shared.isOnboardingCompleted
Sequence.shared.resetOnboarding()

Sequence Client

The Sequence.shared singleton manages SDK state, API communication, and event tracking. Initialize it in your app's entry point.

Methods

MethodParametersReturnsDescription
configure()appId: String, apiKey: String, baseURL: String?VoidInitialize the SDK with your credentials
fetchConfig()noneasync throws -> OnboardingConfigFetch onboarding configuration from API
identify()userId: StringVoidAssociate events with a user ID
reset()noneVoidClear user data and completion status
track()_ eventType: EventType, screenId: String?, properties: [String: Any]?VoidTrack a custom event

Usage Examples

Using the Sequence singleton
import Sequence

class AuthManager {
    // Identify user after login
    func handleLogin(userId: String) {
        Sequence.shared.identify(userId: userId)
    }

    // Track custom event
    func handleButtonPress() {
        Sequence.shared.track(.buttonTapped, screenId: "welcome-screen", properties: [
            "button_label": "Get Started"
        ])
    }

    // Reset on logout
    func handleLogout() {
        Sequence.shared.reset()
    }
}

Configuration

Configure the Sequence SDK in your app's entry point. The configuration must be called before using any other SDK methods.

Parameters

ParameterTypeRequiredDescription
appIdStringYesYour app ID from the dashboard
apiKeyStringYesYour API key (keep secret)
baseURLString?NoCustom API URL (optional)

Configuration Example

import Sequence

// Configure in your App init
Sequence.shared.configure(
    appId: "your-app-id",
    apiKey: "your-api-key",
    baseURL: "https://screensequence.com"
)

Properties

Observable Properties

Sequence.shared is an ObservableObject, so you can use @StateObject or @ObservedObject to observe changes.

import SwiftUI
import Sequence

struct MyView: View {
    @StateObject private var sequence = Sequence.shared

    var body: some View {
        VStack {
            // Observe configuration state
            if sequence.isConfigured {
                Text("SDK is configured")
            }

            // Observe loading state
            if sequence.isLoading {
                ProgressView()
            }

            // Observe onboarding completion
            if sequence.isOnboardingCompleted {
                MainAppView()
            } else {
                OnboardingView { }
            }

            // Access screens
            Text("\(sequence.screens.count) screens in this flow")

            // Access experiment info
            if let experiment = sequence.experimentInfo {
                Text("Variant: \(experiment.variantName)")
            }
        }
    }
}

Available Properties

PropertyTypeDescription
isConfiguredBoolSDK has been initialized
isLoadingBoolCurrently fetching config
errorError?Last error encountered
configOnboardingConfig?Current onboarding configuration
screens[Screen]Array of onboarding screens
isOnboardingCompletedBoolWhether onboarding is complete
experimentInfoExperimentInfo?A/B test variant information

Views

WebViewOnboardingView (Recommended)

Full-screen SwiftUI view that renders your onboarding flow using a WebView for pixel-perfect WYSIWYG rendering. This is the recommended view to use for production apps.

ParameterTypeRequiredDescription
onComplete(() -&gt; Void)?NoCalled when onboarding completes
onDataCollected(([String: Any]) -&gt; Void)?NoCalled with collected user data

Why WebViewOnboardingView?

WebViewOnboardingView renders your flows exactly as designed in the editor with proper scaling on all device sizes. Always use this instead of OnboardingView for production apps.
WebViewOnboardingView example
import SwiftUI
import Sequence

struct ContentView: View {
    @StateObject private var sequence = Sequence.shared

    var body: some View {
        Group {
            if sequence.isOnboardingCompleted {
                MainAppView()
            } else {
                WebViewOnboardingView(
                    onComplete: {
                        print("Onboarding completed!")
                    },
                    onDataCollected: { data in
                        print("User data:", data)
                        saveToBackend(data)
                    }
                )
            }
        }
    }
}

OnboardingView (Legacy)

Native SwiftUI view that renders an approximation of your onboarding flow. Not recommended for production use as it may not match the editor exactly.

Avoid OnboardingView

OnboardingView uses native SwiftUI components which may not render identically to what you see in the editor. Always use WebViewOnboardingView for production apps.

Delegate Protocol

Implement SequenceDelegate to handle custom screens and actions.

import Sequence

class MyAppDelegate: SequenceDelegate {
    // Handle custom native screens
    func sequence(_ sequence: Sequence, didRequestNativeScreen screenId: String, data: [String: Any]) -> Bool {
        if screenId == "auth-screen" {
            showAuthFlow()
            return true // We're handling this
        }
        return false // Let Sequence handle it
    }

    // Handle custom button actions
    func sequence(_ sequence: Sequence, didTriggerCustomAction action: String, screenId: String) {
        if action == "open-settings" {
            if let url = URL(string: UIApplication.openSettingsURLString) {
                UIApplication.shared.open(url)
            }
        }
    }
}

// Set the delegate
Sequence.shared.delegate = MyAppDelegate()

Swift Types

Key types used in the Swift SDK:

Core Types
// Onboarding configuration from API
struct OnboardingConfig: Codable {
    let version: Int
    let screens: [Screen]
    let progressIndicator: FlowProgressIndicator?
    let transitions: [ScreenTransitionConfig]?
    let experiment: ExperimentInfo?
}

// Individual screen
struct Screen: Codable, Identifiable {
    let id: String
    let appId: String
    let flowId: String?
    let name: String
    let type: ScreenType
    let order: Int
    let content: ScreenContent
    let transition: ScreenTransition?
    let createdAt: String
    let updatedAt: String
}

enum ScreenType: String, Codable {
    case welcome
    case feature
    case carousel
    case permission
    case celebration
    case native
    case filler
}

// Event types for tracking
enum EventType {
    case screenViewed
    case screenFirstViewed
    case screenCompleted
    case screenSkipped
    case screenDroppedOff
    case buttonTapped
    case onboardingStarted
    case onboardingCompleted
    case experimentVariantAssigned
}
Block Types
enum BlockType: String, Codable {
    case text
    case image
    case button
    case input
    case checklist
    case slider
    case progress
    case spacer
    case divider
    case icon
    case featureCard = "feature-card"
}

// Experiment info
struct ExperimentInfo: Codable {
    let id: String
    let variantId: String
    let variantName: String
}

Full Type Definitions

For complete type definitions, check the Swift SDK source code or use Xcode's code completion.
Sequence Documentation
Found an issue? Let us know