feat: Refactor UI components and improve animations for onboarding and authentication flows
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import SwiftUI
|
||||
|
||||
/// UIPageViewController wrapper that handles paging entirely in UIKit.
|
||||
/// SwiftUI state is only updated after a page fully settles — zero overhead during swipe.
|
||||
/// Exposes both the settled page index AND a continuous drag progress for smooth dot tracking.
|
||||
struct OnboardingPager<Page: View>: UIViewControllerRepresentable {
|
||||
@Binding var currentIndex: Int
|
||||
/// Continuous progress: 0.0 = page 0, 1.0 = page 1, etc. Tracks the finger in real time.
|
||||
@Binding var continuousProgress: CGFloat
|
||||
let count: Int
|
||||
let buildPage: (Int) -> Page
|
||||
|
||||
@@ -22,9 +24,13 @@ struct OnboardingPager<Page: View>: UIViewControllerRepresentable {
|
||||
direction: .forward,
|
||||
animated: false
|
||||
)
|
||||
// Make the inner scroll view transparent
|
||||
// Hook into the inner scroll view for real-time offset tracking
|
||||
for sub in vc.view.subviews {
|
||||
(sub as? UIScrollView)?.backgroundColor = .clear
|
||||
if let scrollView = sub as? UIScrollView {
|
||||
scrollView.backgroundColor = .clear
|
||||
scrollView.delegate = context.coordinator
|
||||
context.coordinator.pageWidth = scrollView.frame.width
|
||||
}
|
||||
}
|
||||
return vc
|
||||
}
|
||||
@@ -35,18 +41,28 @@ struct OnboardingPager<Page: View>: UIViewControllerRepresentable {
|
||||
|
||||
// MARK: - Coordinator
|
||||
|
||||
final class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
|
||||
final class Coordinator: NSObject,
|
||||
UIPageViewControllerDataSource,
|
||||
UIPageViewControllerDelegate,
|
||||
UIScrollViewDelegate
|
||||
{
|
||||
var parent: OnboardingPager
|
||||
let controllers: [UIHostingController<Page>]
|
||||
var pageWidth: CGFloat = 0
|
||||
private var pendingIndex: Int = 0
|
||||
|
||||
init(_ parent: OnboardingPager) {
|
||||
self.parent = parent
|
||||
// Create hosting controllers without triggering view loading
|
||||
self.pendingIndex = parent.currentIndex
|
||||
self.controllers = (0..<parent.count).map { i in
|
||||
UIHostingController(rootView: parent.buildPage(i))
|
||||
let hc = UIHostingController(rootView: parent.buildPage(i))
|
||||
hc.view.backgroundColor = .clear
|
||||
return hc
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: DataSource
|
||||
|
||||
func pageViewController(
|
||||
_ pvc: UIPageViewController,
|
||||
viewControllerBefore vc: UIViewController
|
||||
@@ -64,6 +80,18 @@ struct OnboardingPager<Page: View>: UIViewControllerRepresentable {
|
||||
return controllers[idx + 1]
|
||||
}
|
||||
|
||||
// MARK: Delegate
|
||||
|
||||
func pageViewController(
|
||||
_ pvc: UIPageViewController,
|
||||
willTransitionTo pendingVCs: [UIViewController]
|
||||
) {
|
||||
if let vc = pendingVCs.first,
|
||||
let idx = controllers.firstIndex(where: { $0 === vc }) {
|
||||
pendingIndex = idx
|
||||
}
|
||||
}
|
||||
|
||||
func pageViewController(
|
||||
_ pvc: UIPageViewController,
|
||||
didFinishAnimating finished: Bool,
|
||||
@@ -75,5 +103,18 @@ struct OnboardingPager<Page: View>: UIViewControllerRepresentable {
|
||||
let idx = controllers.firstIndex(where: { $0 === current }) else { return }
|
||||
parent.currentIndex = idx
|
||||
}
|
||||
|
||||
// MARK: ScrollView — real-time progress
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
let w = scrollView.frame.width
|
||||
guard w > 0 else { return }
|
||||
// UIPageViewController's scroll view rests at x = width.
|
||||
// Dragging left (next page) increases x; dragging right decreases.
|
||||
let offsetFromCenter = scrollView.contentOffset.x - w
|
||||
let fraction = offsetFromCenter / w
|
||||
let progress = CGFloat(parent.currentIndex) + fraction
|
||||
parent.continuousProgress = max(0, min(CGFloat(parent.count - 1), progress))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user