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

Popular posts from this blog

Dependency Injection in iOS with SwiftUI

Using Core ML with SwiftUI: Build an AI-Powered App

CI/CD for iOS Projects with Xcode: A Complete Guide