Create Stunning Onboarding Screens in iOS with PageViewController and Animations using SwiftUI
Onboarding screens set the tone for your app. Let’s make yours unforgettable with smooth page transitions and delightful animations using SwiftUI and UIPageViewController integration.
✨ Why Beautiful Onboarding Screens Matter
In today’s competitive app ecosystem, the first impression is everything. A well-designed onboarding experience can:
- Increase user retention
- Improve app engagement
- Communicate app value instantly
Using SwiftUI alone has limitations for paginated onboarding. But by bridging UIPageViewController, we unlock incredible flexibility, smooth page swipes, and animated transitions.
š§ What You’ll Learn
- How to use
UIPageViewController
in SwiftUI - Create animated onboarding pages
- Add page indicators
- Make reusable onboarding components
- Add smooth transitions
š± Final Result Preview
Imagine a swipeable, animated onboarding screen like this:
- š Welcome message on the first screen
- šØ Eye-catching illustrations with animations
- šÆ Final call to action screen
Let’s build this in 5 simple steps.
1️⃣ Setting Up the Onboarding Data Model
struct OnboardingPage: Identifiable {
let id = UUID()
let title: String
let description: String
let imageName: String
let backgroundColor: Color
}
Sample pages:
let onboardingPages = [
OnboardingPage(title: "Welcome", description: "Discover new features", imageName: "onboard1", backgroundColor: .blue),
OnboardingPage(title: "Stay Organized", description: "Manage your tasks", imageName: "onboard2", backgroundColor: .green),
OnboardingPage(title: "Achieve More", description: "Boost productivity", imageName: "onboard3", backgroundColor: .purple)
]
2️⃣ Create the Onboarding Page View
import SwiftUI
struct OnboardingPageView: View {
let page: OnboardingPage
var body: some View {
VStack(spacing: 20) {
Image(page.imageName)
.resizable()
.scaledToFit()
.frame(height: 250)
.transition(.scale.combined(with: .opacity))
Text(page.title)
.font(.largeTitle)
.bold()
.foregroundColor(.white)
Text(page.description)
.font(.body)
.multilineTextAlignment(.center)
.foregroundColor(.white.opacity(0.8))
.padding(.horizontal)
}
.padding()
.background(page.backgroundColor)
.cornerRadius(30)
.shadow(radius: 10)
}
}
3️⃣ Wrap UIPageViewController in SwiftUI
import SwiftUI
import UIKit
struct PageViewController: UIViewControllerRepresentable {
var pages: [UIViewController]
@Binding var currentPage: Int
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> UIPageViewController {
let pageVC = UIPageViewController(
transitionStyle: .scroll,
navigationOrientation: .horizontal
)
pageVC.dataSource = context.coordinator
pageVC.delegate = context.coordinator
pageVC.setViewControllers([pages[currentPage]], direction: .forward, animated: true)
return pageVC
}
func updateUIViewController(_ uiViewController: UIPageViewController, context: Context) {
uiViewController.setViewControllers(
[pages[currentPage]],
direction: .forward,
animated: true
)
}
class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var parent: PageViewController
init(_ parent: PageViewController) {
self.parent = parent
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let index = parent.pages.firstIndex(of: viewController), index > 0 else {
return nil
}
return parent.pages[index - 1]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let index = parent.pages.firstIndex(of: viewController), index + 1 < parent.pages.count else {
return nil
}
return parent.pages[index + 1]
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed, let visibleVC = pageViewController.viewControllers?.first, let index = parent.pages.firstIndex(of: visibleVC) {
parent.currentPage = index
}
}
}
}
4️⃣ Final Onboarding Container with Page Control
struct OnboardingContainerView: View {
@State private var currentPage = 0
var body: some View {
VStack {
PageViewController(
pages: onboardingPages.map { UIHostingController(rootView: OnboardingPageView(page: $0)) },
currentPage: $currentPage
)
.frame(height: 500)
HStack(spacing: 8) {
ForEach(0..<onboardingPages.count, id: \.self) { index in
Circle()
.fill(index == currentPage ? Color.white : Color.gray.opacity(0.5))
.frame(width: 10, height: 10)
.scaleEffect(index == currentPage ? 1.2 : 1.0)
.animation(.spring(), value: currentPage)
}
}
.padding(.top, 20)
if currentPage == onboardingPages.count - 1 {
Button("Get Started") {
// Navigate to your main app
}
.padding()
.foregroundColor(.white)
.background(Color.black)
.cornerRadius(10)
.transition(.move(edge: .bottom).combined(with: .opacity))
.animation(.easeInOut, value: currentPage)
}
}
}
}
š” Pro Tips
- Use Lottie animations for more interactive visuals.
- Add accessibility for screen reader users.
- Test onboarding on different screen sizes.
š Final Thoughts
SwiftUI + UIPageViewController
= powerful onboarding experiences. Use this hybrid approach to delight users right from their first interaction with your app. The smoother the start, the longer they'll stay.
Comments
Post a Comment