🚀 Learn how to build and use this package: https://www.swiftful-thinking.com/offers/REyNLwwH
- ✅ Multiple slide types (Regular, Multiple Choice, Yes/No, Rating, Text Input, Primary Action)
- ✅ Real-time feedback & response screens
- ✅ Dynamic slide insertion based on user responses
- ✅ Fully customizable styling and layouts
- ✅ Built-in support for images, Lottie animations, and system icons
- ✅ Progress tracking with callbacks
Details (Click to expand)
Create a simple onboarding flow:
import SwiftfulOnboarding
struct ContentView: View {
var body: some View {
SwiftfulOnboardingView(
configuration: OnbConfiguration(
slides: [
.regular(
id: "welcome",
title: "Welcome!",
subtitle: "Get started with our amazing app",
media: .systemIcon(named: "star.fill")
),
.multipleChoice(
id: "interests",
title: "What are you interested in?",
options: [
OnbChoiceOption(id: "tech", content: OnbButtonContentData(text: "Technology")),
OnbChoiceOption(id: "design", content: OnbButtonContentData(text: "Design")),
OnbChoiceOption(id: "business", content: OnbButtonContentData(text: "Business"))
]
),
.rating(
id: "rate",
title: "How excited are you?",
contentAlignment: .top,
ratingButtonOption: .number
)
]
)
)
}
}- iOS 15.0+ / macOS 12.0+
- Swift 5.5+
- Xcode 13.0+
dependencies: [
.package(url: "https://github.com/SwiftfulThinking/SwiftfulOnboarding.git", branch: "main")
]Details (Click to expand)
Import the package:
import SwiftfulOnboardingCreate an onboarding configuration:
let config = OnbConfiguration(
headerConfiguration: OnbHeaderConfiguration(
headerStyle: .progressBar,
headerAlignment: .center,
showBackButton: .afterFirstSlide
),
slides: [
// Your slides here
]
)Display the onboarding view:
SwiftfulOnboardingView(configuration: config)Details (Click to expand)
Simple informational slide with optional media:
.regular(
id: "welcome",
title: "Welcome to Our App",
subtitle: "Discover amazing features",
media: .systemIcon(named: "star.fill"),
mediaPosition: .top,
contentAlignment: .center
)Present users with multiple options:
.multipleChoice(
id: "interests",
title: "Select your interests",
subtitle: "Choose all that apply",
options: [
OnbChoiceOption(
id: "tech",
content: OnbButtonContentData(text: "Technology", icon: .systemIcon(named: "laptopcomputer"))
),
OnbChoiceOption(
id: "design",
content: OnbButtonContentData(text: "Design", icon: .systemIcon(named: "paintbrush"))
),
OnbChoiceOption(
id: "business",
content: OnbButtonContentData(text: "Business", icon: .systemIcon(named: "briefcase"))
)
],
selectionBehavior: .multi(max: 3),
contentAlignment: .top
)Multiple choice slides support:
- Single selection:
.single(autoAdvance: Bool)- Select one option - Multi selection:
.multi(max: Int?)- Select multiple options with optional maximum limitmax: nil(default) - Unlimited selectionsmax: 3- Limit to maximum 3 selections
- Grid layout: Display options in a grid
- Custom button styles: Duolingo-style, solid, outline, solidOutline
- Checkboxes: Circle or square checkboxes for multi-select
Binary choice with custom labels:
.yesNo(
id: "notifications",
title: "Enable Notifications?",
subtitle: "Stay updated with the latest news",
yesText: "Yes, please",
noText: "Maybe later",
contentAlignment: .top
)Collect user ratings:
.rating(
id: "satisfaction",
title: "How satisfied are you?",
subtitle: "Your feedback helps us improve",
contentAlignment: .top,
ratingButtonOption: .number, // or .thumbs
ratingLabels: RatingFooterLabels(
left: "Poor",
right: "Excellent"
),
selectionBehavior: .single(autoAdvance: false)
)Rating options:
- Number rating: 1-5 scale with numbers
- Thumbs rating: Thumbs up/down (2-point scale)
- Custom labels: Add context to rating endpoints
- Label placement: Top or bottom of rating buttons
Collect user text input:
.textInput(
id: "name",
title: "What's your name?",
subtitle: "We'd love to get to know you",
textfieldPlaceholder: "Enter your name",
textfieldIcon: .systemIcon(named: "person"),
contentAlignment: .top
)Call-to-action with optional secondary button:
.primaryAction(
id: "get-started",
title: "You're All Set!",
subtitle: "Ready to begin your journey?",
media: .systemIcon(named: "checkmark.circle.fill"),
ctaText: "Get Started",
secondaryButtonText: "Skip for now",
contentAlignment: .center
)Details (Click to expand)
Show inline feedback based on user selections:
.multipleChoice(
id: "quiz",
title: "What is 2 + 2?",
options: [
OnbChoiceOption(
id: "correct",
content: OnbButtonContentData(text: "4"),
feedbackConfiguration: OnbFeedbackConfiguration(
backgroundColor: .green.opacity(0.2),
cornerRadius: 4,
title: "Correct!",
subtitle: "Great job"
)
),
OnbChoiceOption(
id: "incorrect",
content: OnbButtonContentData(text: "5"),
feedbackConfiguration: OnbFeedbackConfiguration(
backgroundColor: .red.opacity(0.2),
cornerRadius: 4,
title: "Oops!",
subtitle: "Try again"
)
)
],
feedbackStyle: .top() // or .bottom()
)Feedback styles:
.top(transition: .none/.slide/.opacity)- Appears above content.bottom(transition: .none/.slide/.opacity)- Appears above rating/options
Show full-screen response screens:
.multipleChoice(
id: "subscribe",
title: "Choose your plan",
options: [
OnbChoiceOption(
id: "premium",
content: OnbButtonContentData(text: "Premium Plan"),
responseConfiguration: OnbResponseConfiguration(
style: .center(transition: .slide),
backgroundColor: .blue,
horizontalPadding: 24,
title: "Welcome to Premium!",
titleFont: .largeTitle,
subtitle: "You've made an excellent choice",
ctaText: "Continue",
ctaButtonStyle: .solid(backgroundColor: .white, textColor: .blue)
)
)
],
selectionBehavior: .single(autoAdvance: true)
)Response styles:
.center(transition: .slide/.opacity/.fade/.scale)- Centered full-screen.bottom(transition: .bottom)- Bottom sheet style
Insert slides based on user responses:
.multipleChoice(
id: "experience",
title: "How experienced are you?",
options: [
OnbChoiceOption(
id: "beginner",
content: OnbButtonContentData(text: "Beginner"),
insertConfiguration: [
InsertSlideData(
location: .next,
slide: .regular(
id: "beginner-tip",
title: "Tips for Beginners",
subtitle: "Here's what you need to know"
)
)
]
),
OnbChoiceOption(
id: "expert",
content: OnbButtonContentData(text: "Expert"),
insertConfiguration: [
InsertSlideData(
location: .next,
slide: .regular(
id: "expert-features",
title: "Advanced Features",
subtitle: "Unlock your full potential"
)
)
]
)
]
)Insert locations:
.next- Insert immediately after current slide.afterId(String)- Insert after specific slide ID.atEnd- Append to end of flow
Track user progress through the flow:
let config = OnbConfiguration(
slides: slides,
onSlideComplete: { slideId, userSelections in
print("Completed slide: \(slideId)")
print("User selections: \(userSelections)")
},
onFlowComplete: { allSelections in
print("Onboarding complete!")
print("All user data: \(allSelections)")
// Save user preferences, navigate to main app, etc.
}
)Details (Click to expand)
Set default styling for all slides:
OnbConfiguration(
slideDefaults: OnbSlideDefaults(
titleFont: .system(.title, weight: .bold),
subtitleFont: .body,
titleAlignment: .center,
contentAlignment: .center,
paddingTop: 40,
paddingBottom: 0,
horizontalPaddingContent: 24,
contentSpacing: 12,
ctaButtonStyle: .solid(backgroundColor: .blue, textColor: .white),
ctaButtonFormatData: OnbButtonFormatData(
pressStyle: .press,
font: .headline,
height: .verticalPadding(16),
cornerRadius: 12
),
background: .solidColor(.clear),
transitionStyle: .slide
),
slides: slides
)Customize the header:
OnbHeaderConfiguration(
headerStyle: .progressBar, // .progressBar, .count, or .none
headerAlignment: .center, // .center or .right
showBackButton: .afterFirstSlide, // .always, .afterFirstSlide, or .never
backButtonColor: .blue,
progressBarAccentColor: .blue
)Multiple button style options:
// Solid style
.solid(
backgroundColor: Color(uiColor: .systemGray5),
textColor: .black,
selectedBackgroundColor: .blue,
selectedTextColor: .white
)
// Outline style
.outline(
textColor: .blue,
borderColor: .blue,
borderWidth: 2,
selectedTextColor: .white,
selectedBackgroundColor: .blue
)
// Solid with outline
.solidOutline(
backgroundColor: .white,
textColor: .black,
borderColor: .gray,
borderWidth: 1,
selectedBackgroundColor: .blue,
selectedTextColor: .white,
selectedBorderColor: .blue
)
// Duolingo style (checkbox-based)
.duolingo(
backgroundColor: .white,
textColor: .black,
borderColor: .gray,
checkboxStyle: .circle, // or .square
selectedTextColor: .blue,
selectedBorderColor: .blue
)Customize button appearance:
OnbButtonFormatData(
pressStyle: .press, // .press, .opacity, or .none
font: .headline,
height: .verticalPadding(16), // or .fixed(50)
cornerRadius: 12
)Set slide backgrounds:
// Solid color
.solidColor(.blue)
// Gradient
.gradient(
Gradient(colors: [.purple, .blue]),
startPoint: .topLeading,
endPoint: .bottomTrailing
)
// Image
.image(urlString: "https://example.com/background.jpg")Support for various media types:
// System icon
.systemIcon(named: "star.fill", size: .large)
// Image URL
.image(urlString: "https://example.com/image.jpg")
// Lottie animation
.lottie(urlString: "https://example.com/animation.json", loopMode: .loop)Media sizes: .small, .medium, .large, .extraLarge, .custom(CGFloat)
Animate between slides:
.slide // Horizontal slide (default)
.opacity // Fade in/out
.fade // Fade with subtle offset
.none // No animationDetails (Click to expand)
OnbConfiguration(
slides: [
.regular(
id: "welcome",
title: "Welcome",
subtitle: "Get started with our app"
),
.regular(
id: "features",
title: "Key Features",
subtitle: "Discover what you can do"
),
.regular(
id: "ready",
title: "Ready to Go!",
subtitle: "Let's dive in"
)
]
)OnbConfiguration(
slides: [
.multipleChoice(
id: "interests",
title: "What interests you?",
options: [
OnbChoiceOption(id: "tech", content: OnbButtonContentData(text: "Technology")),
OnbChoiceOption(id: "design", content: OnbButtonContentData(text: "Design")),
OnbChoiceOption(id: "business", content: OnbButtonContentData(text: "Business"))
],
selectionBehavior: .multi(min: 1, max: 3)
),
.rating(
id: "satisfaction",
title: "How likely are you to recommend us?",
contentAlignment: .top,
ratingButtonOption: .number
),
.textInput(
id: "feedback",
title: "Any additional feedback?",
textfieldPlaceholder: "Share your thoughts..."
)
],
onFlowComplete: { selections in
print("Survey complete: \(selections)")
}
)OnbConfiguration(
slides: [
.yesNo(
id: "notifications",
title: "Enable Notifications?",
yesOption: OnbChoiceOption(
id: "yes",
content: OnbButtonContentData(text: "Yes"),
insertConfiguration: [
InsertSlideData(
location: .next,
slide: .multipleChoice(
id: "notification-types",
title: "What notifications?",
options: [
OnbChoiceOption(id: "all", content: OnbButtonContentData(text: "All")),
OnbChoiceOption(id: "important", content: OnbButtonContentData(text: "Important Only"))
]
)
)
]
),
noOption: OnbChoiceOption(id: "no", content: OnbButtonContentData(text: "No"))
),
.primaryAction(
id: "complete",
title: "All Set!",
ctaText: "Get Started"
)
]
)Details (Click to expand)
Track user progress and collect data using callback functions:
Called each time a user completes a slide (by clicking Continue or auto-advancing):
OnbConfiguration(
slides: slides,
onSlideComplete: { slideId, userSelections in
print("User completed slide: \(slideId)")
print("Their selections: \(userSelections)")
// Example: Save progress to UserDefaults
UserDefaults.standard.set(slideId, forKey: "lastCompletedSlide")
// Example: Send analytics event
analytics.track("slide_completed", properties: [
"slide_id": slideId,
"selections": userSelections
])
}
)Parameters:
slideId: String- The ID of the slide that was just completeduserSelections: [String: [OnbChoiceOption]]- Dictionary of all user selections up to this point, keyed by slide ID
Use cases:
- Track user progress through the flow
- Save partial completion state
- Send analytics events per slide
- Update UI outside the onboarding flow
- Validate user input before proceeding
Called when the user completes the entire onboarding flow (reaches the last slide and clicks Continue):
OnbConfiguration(
slides: slides,
onFlowComplete: { allSelections in
print("Onboarding complete!")
print("All user data: \(allSelections)")
// Example: Save user preferences
let interests = allSelections["interests"]?.map { $0.id } ?? []
let notificationsEnabled = allSelections["notifications"]?.first?.id == "yes"
let userName = allSelections["name"]?.first?.content.text ?? ""
UserDefaults.standard.set(interests, forKey: "userInterests")
UserDefaults.standard.set(notificationsEnabled, forKey: "notificationsEnabled")
UserDefaults.standard.set(userName, forKey: "userName")
// Example: Navigate to main app
isOnboardingComplete = true
// Example: Send completion event
analytics.track("onboarding_completed", properties: [
"total_slides": allSelections.count,
"interests_count": interests.count
])
}
)Parameters:
allSelections: [String: [OnbChoiceOption]]- Dictionary of ALL user selections from the entire flow, keyed by slide ID
Use cases:
- Save all user preferences at once
- Navigate to the main app
- Create user profile from collected data
- Send completion analytics
- Trigger welcome emails or notifications
Each OnbChoiceOption in the selections contains:
struct OnbChoiceOption {
let id: String // Option identifier
let content: OnbButtonContentData // Button content (text, icon, etc.)
// ... other properties
}
struct OnbButtonContentData {
var text: String? // Button text
var icon: OnbMediaType? // Button icon
var value: Any? // Custom value you can attach
// ... other properties
}Example: Processing selections
onFlowComplete: { allSelections in
// Get selected interests
if let interestOptions = allSelections["interests"] {
let interestIds = interestOptions.map { $0.id }
let interestTexts = interestOptions.compactMap { $0.content.text }
print("User interests: \(interestTexts)")
}
// Get rating value
if let ratingOption = allSelections["satisfaction"]?.first,
let rating = ratingOption.content.value as? Int {
print("User rated: \(rating)/5")
}
// Get text input
if let nameOption = allSelections["name"]?.first,
let name = nameOption.content.text {
print("User name: \(name)")
}
// Get yes/no answer
if let notificationOption = allSelections["notifications"]?.first {
let enabled = notificationOption.id == "yes"
print("Notifications enabled: \(enabled)")
}
}struct OnboardingCoordinator: View {
@State private var showOnboarding = true
@State private var userProfile: UserProfile?
var body: some View {
if showOnboarding {
SwiftfulOnboardingView(
configuration: OnbConfiguration(
slides: onboardingSlides,
onSlideComplete: { slideId, selections in
// Track progress
print("Completed: \(slideId)")
},
onFlowComplete: { allSelections in
// Process all data
userProfile = UserProfile(from: allSelections)
// Dismiss onboarding
showOnboarding = false
}
)
)
} else {
MainAppView(userProfile: userProfile)
}
}
}Community contributions are encouraged! Please ensure that your code adheres to the project's existing coding style and structure.
- Open an issue for issues with the existing codebase.
- Open a discussion for new feature requests.
- Submit a pull request when the feature is ready.
MIT License. See LICENSE file for details.