iOS应用引导页:如何从UIPageViewController跳转至UINavigationController
嘿,我来帮你搞定这个Onboarding跳转的问题!其实实现思路挺清晰的,这里给你几种靠谱的方案,你可以根据自己的项目结构选最合适的:
方案一:使用Delegate回调(最推荐,解耦性强)
这种方式是iOS开发里处理组件间通信的标准做法,能让你的Onboarding页面和主逻辑完全解耦,维护起来更方便。
- 首先给你的
OnboardingPageViewController定义一个协议,用来回调完成事件:
protocol OnboardingPageViewControllerDelegate: AnyObject { func onboardingDidComplete() }
- 在
OnboardingPageViewController里添加一个弱引用的代理属性,然后在完成按钮的点击事件里调用代理方法:
class OnboardingPageViewController: UIPageViewController { // 弱引用避免循环引用 weak var onboardingDelegate: OnboardingPageViewControllerDelegate? // 假设你的完成按钮是通过IBAction关联的 @IBAction func completeButtonTapped(_ sender: UIButton) { // 触发完成回调 onboardingDelegate?.onboardingDidComplete() } }
- 在负责管理窗口的类(iOS 13+用
SceneDelegate,iOS 12及以下用AppDelegate)里遵守这个协议,初始化Onboarding时设置代理,并实现回调方法切换根控制器:
class SceneDelegate: UIResponder, UIWindowSceneDelegate, OnboardingPageViewControllerDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } let window = UIWindow(windowScene: windowScene) self.window = window // 先判断用户是否已经完成过Onboarding(用UserDefaults存标记) let hasCompletedOnboarding = UserDefaults.standard.bool(forKey: "HasCompletedOnboarding") if hasCompletedOnboarding { // 直接加载主导航控制器 let mainNavVC = MainNavigationController() window.rootViewController = mainNavVC } else { // 初始化Onboarding页面并设置代理 let onboardingVC = OnboardingPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal) onboardingVC.onboardingDelegate = self window.rootViewController = onboardingVC } window.makeKeyAndVisible() } // 实现代理方法,完成Onboarding后切换到主导航 func onboardingDidComplete() { // 标记Onboarding已完成,下次启动不再显示 UserDefaults.standard.set(true, forKey: "HasCompletedOnboarding") UserDefaults.standard.synchronize() // 创建主导航控制器,加个过渡动画提升体验 let mainNavVC = MainNavigationController() UIView.transition(with: window!, duration: 0.5, options: .transitionCrossDissolve, animations: { self.window?.rootViewController = mainNavVC }, completion: nil) } }
方案二:使用NotificationCenter通知(适合跨层级场景)
如果你的Onboarding页面嵌套层级比较深,用Delegate不太方便的话,可以用通知来触发跳转,不过这种方式耦合度会比Delegate高一点。
- 在完成按钮的点击事件里发送通知:
@IBAction func completeButtonTapped(_ sender: UIButton) { // 标记完成状态 UserDefaults.standard.set(true, forKey: "HasCompletedOnboarding") // 发送完成通知 NotificationCenter.default.post(name: NSNotification.Name("OnboardingDidComplete"), object: nil) }
- 在
SceneDelegate里监听这个通知,并处理跳转:
class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { // ... 前面的窗口初始化和Onboarding判断逻辑 // 添加通知监听 NotificationCenter.default.addObserver(self, selector: #selector(onboardingCompleted), name: NSNotification.Name("OnboardingDidComplete"), object: nil) } @objc private func onboardingCompleted() { let mainNavVC = MainNavigationController() // 切换根控制器并加动画 UIView.transition(with: window!, duration: 0.5, options: .transitionCrossDissolve, animations: { self.window?.rootViewController = mainNavVC }, completion: nil) // 记得移除监听,避免内存泄漏 NotificationCenter.default.removeObserver(self, name: NSNotification.Name("OnboardingDidComplete"), object: nil) } }
方案三:直接修改Window的RootViewController(简单粗暴)
如果不想搞代理或通知,也可以在完成按钮点击时直接获取到窗口实例,修改它的根控制器:
@IBAction func completeButtonTapped(_ sender: UIButton) { // 标记完成状态 UserDefaults.standard.set(true, forKey: "HasCompletedOnboarding") // 获取当前窗口实例 guard let sceneDelegate = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate else { return } let mainNavVC = MainNavigationController() // 切换根控制器并加动画 UIView.transition(with: sceneDelegate.window!, duration: 0.5, options: .transitionCrossDissolve, animations: { sceneDelegate.window?.rootViewController = mainNavVC }, completion: nil) }
几个关键注意点
- 一定要记录完成状态:用
UserDefaults或者其他本地存储标记用户是否完成过Onboarding,不然每次启动都会重复显示引导页。 - 添加过渡动画:直接切换根控制器会很生硬,用
UIView.transition加个淡入淡出或者滑动动画,用户体验会好很多。 - iOS版本兼容:如果需要兼容iOS 12及以下,要把窗口管理逻辑放到
AppDelegate里,和SceneDelegate的逻辑基本一致,只是用AppDelegate的window属性。
内容的提问来源于stack exchange,提问作者Rwirth
相关产品推荐
相关产品推荐

