I. iOS UINavigationController 概述

UINavigationController 是 iOS 开发中最常用的控制器之一,它通过栈结构管理视图控制器,并提供了平滑的转场动画。本章将从基本概念入手,深入探讨 UINavigationController 的定义、特性及核心概念。

1.1 UINavigationController 的基本定义

UINavigationController 是 UIViewController 的子类,用于管理一组视图控制器的层次结构。它使用栈(后进先出)的方式管理视图控制器,提供了导航栏、工具栏等组件,并处理视图控制器之间的转场动画。

// UINavigationController 的基本定义(简化版)
@objc open class UINavigationController : UIViewController, UINavigationBarDelegate {// 导航栈open var viewControllers: [UIViewController]// 根视图控制器open var rootViewController: UIViewController? { get }// 当前可见的视图控制器open var visibleViewController: UIViewController? { get }// 顶部视图控制器(栈顶控制器)open var topViewController: UIViewController? { get }// 导航栏open lazy var navigationBar: UINavigationBar// 工具栏open lazy var toolbar: UIToolbar// 是否显示工具栏open var isToolbarHidden: Bool// 初始化方法public init(rootViewController: UIViewController)public init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)// 视图控制器管理方法open func pushViewController(_ viewController: UIViewController, animated: Bool)open func popViewController(animated: Bool) -> UIViewController?open func popToViewController(_ viewController: UIViewController, animated: Bool) -> [UIViewController]?open func popToRootViewController(animated: Bool) -> [UIViewController]?open func setViewControllers(_ viewControllers: [UIViewController], animated: Bool)// 转场动画相关方法open func transition(from fromVC: UIViewController, to toVC: UIViewController, duration: TimeInterval, options: UIView.AnimationOptions = [], animations: (() -> Void)? = nil, completion: ((Bool) -> Void)? = nil)// 导航栏样式相关方法open func setNavigationBarHidden(_ hidden: Bool, animated: Bool)
}
1.2 UINavigationController 的核心特性

UINavigationController 的核心特性包括:

  • 栈式管理:使用栈结构管理视图控制器,支持入栈(push)和出栈(pop)操作。
  • 导航栏:自动管理导航栏,根据栈内视图控制器更新导航栏内容。
  • 转场动画:提供默认的水平滑动转场动画,也支持自定义转场动画。
  • 工具栏:可选的底部工具栏,用于显示额外的导航按钮。
  • 代理机制:通过 UINavigationControllerDelegate 协议,允许自定义转场动画和导航行为。

这些特性使得 UINavigationController 成为构建层次化用户界面的理想选择。

1.3 UINavigationController 的工作流程

UINavigationController 的基本工作流程如下:

  1. 创建 UINavigationController 并设置根视图控制器。
  2. 用户与界面交互,触发视图控制器的入栈或出栈操作。
  3. UINavigationController 管理导航栈,更新导航栏和工具栏内容。
  4. 执行转场动画,显示新的视图控制器。
  5. 重复步骤 2-4,直到用户退出导航控制器。

这个流程体现了 UINavigationController 的核心设计思想:通过栈结构管理视图控制器的层次关系,并提供平滑的过渡体验。

II. UINavigationController 的栈管理机制

UINavigationController 的核心是其栈管理机制。本章将深入分析导航栈的结构、操作和实现原理。

2.1 导航栈的基本结构

导航栈是一个存储 UIViewController 对象的数组,遵循后进先出(LIFO)的原则。

// 导航栈的基本结构(简化版)
private class NavigationStack {private var stack: [UIViewController] = []// 栈顶元素var top: UIViewController? {return stack.last}// 栈是否为空var isEmpty: Bool {return stack.isEmpty}// 栈的大小var count: Int {return stack.count}// 入栈操作func push(_ viewController: UIViewController) {// 添加到栈顶stack.append(viewController)// 通知栈变化notifyStackChanged()}// 出栈操作func pop() -> UIViewController? {guard !isEmpty else { return nil }// 从栈顶移除let viewController = stack.removeLast()// 通知栈变化notifyStackChanged()return viewController}// 弹出到指定视图控制器func popTo(_ viewController: UIViewController) -> [UIViewController]? {// 查找目标视图控制器在栈中的位置guard let index = stack.firstIndex(where: { $0 === viewController }) else {return nil}// 计算需要弹出的视图控制器let poppedControllers = Array(stack.suffix(from: index + 1))// 更新栈stack = Array(stack.prefix(through: index))// 通知栈变化notifyStackChanged()return poppedControllers}// 弹出到根视图控制器func popToRoot() -> [UIViewController]? {guard count > 1 else { return nil }// 保存除根视图控制器外的所有控制器let poppedControllers = Array(stack.dropFirst())// 只保留根视图控制器stack = [stack.first!]// 通知栈变化notifyStackChanged()return poppedControllers}// 设置整个栈func setStack(_ viewControllers: [UIViewController]) {guard !viewControllers.isEmpty else { return }// 保存当前栈let oldStack = stack// 设置新栈stack = viewControllers// 通知栈变化notifyStackChanged(oldStack: oldStack, newStack: stack)}// 通知栈变化private func notifyStackChanged(oldStack: [UIViewController]? = nil, newStack: [UIViewController]? = nil) {// 通知导航控制器栈发生了变化// 导航控制器会根据栈的变化更新界面}
}
2.2 视图控制器的入栈操作

当调用 pushViewController(_:animated:) 方法时,UINavigationController 会执行以下操作:

// pushViewController 方法的实现(简化版)
extension UINavigationController {open func pushViewController(_ viewController: UIViewController, animated: Bool) {// 检查是否已经在导航栈中guard !viewControllers.contains(where: { $0 === viewController }) else {// 处理重复 push 的情况return}// 检查是否需要自定义转场动画let transitionCoordinator = self.transitionCoordinator// 准备新视图控制器prepareViewControllerForPush(viewController)// 开始转场动画if animated {performAnimatedPush(of: viewController, coordinator: transitionCoordinator)} else {performNonAnimatedPush(of: viewController)}// 更新导航栏updateNavigationBar(for: viewController)// 更新工具栏updateToolbar(for: viewController)// 通知代理delegate?.navigationController?(self, didShow: viewController, animated: animated)}private func prepareViewControllerForPush(_ viewController: UIViewController) {// 设置新视图控制器的导航控制器viewController.navigationController = self// 设置新视图控制器的导航项setupNavigationItem(for: viewController)// 将视图控制器添加到导航栈navigationStack.push(viewController)}private func performAnimatedPush(of viewController: UIViewController, coordinator: UIViewControllerTransitionCoordinator?) {// 获取当前可见的视图控制器let fromViewController = visibleViewController// 创建转场上下文let transitionContext = createTransitionContext(from: fromViewController, to: viewController, isPush: true)// 获取转场动画控制器let animator = transitionAnimator(for: transitionContext)// 设置转场动画transition(from: fromViewController!, to: viewController, duration: animator.duration, options: [], animations: {// 执行转场动画animator.animateTransition(using: transitionContext)}, completion: { finished in// 完成转场动画self.completeTransition(transitionContext, animated: true)})}private func performNonAnimatedPush(of viewController: UIViewController) {// 获取当前可见的视图控制器let fromViewController = visibleViewController// 直接添加新视图控制器的视图addChild(viewController)view.addSubview(viewController.view)viewController.didMove(toParent: self)// 如果有当前视图控制器,将其移除if let fromViewController = fromViewController {fromViewController.willMove(toParent: nil)fromViewController.view.removeFromSuperview()fromViewController.removeFromParent()}}
}
2.3 视图控制器的出栈操作

当调用 popViewController(animated:) 方法时,UINavigationController 会执行以下操作:

// popViewController 方法的实现(简化版)
extension UINavigationController {open func popViewController(animated: Bool) -> UIViewController? {// 获取当前栈顶视图控制器guard let topViewController = topViewController, viewControllers.count > 1 else {// 如果栈中只有一个视图控制器,不执行弹出操作return nil}// 获取要显示的新视图控制器(栈顶的下一个控制器)let newTopViewController = viewControllers[viewControllers.count - 2]// 检查是否需要自定义转场动画let transitionCoordinator = self.transitionCoordinator// 开始转场动画if animated {performAnimatedPop(from: topViewController, to: newTopViewController, coordinator: transitionCoordinator)} else {performNonAnimatedPop(from: topViewController, to: newTopViewController)}// 更新导航栏updateNavigationBar(for: newTopViewController)// 更新工具栏updateToolbar(for: newTopViewController)// 通知代理delegate?.navigationController?(self, didShow: newTopViewController, animated: animated)// 返回被弹出的视图控制器return topViewController}private func performAnimatedPop(from fromViewController: UIViewController, to toViewController: UIViewController, coordinator: UIViewControllerTransitionCoordinator?) {// 创建转场上下文let transitionContext = createTransitionContext(from: fromViewController, to: toViewController, isPush: false)// 获取转场动画控制器let animator = transitionAnimator(for: transitionContext)// 设置转场动画transition(from: fromViewController, to: toViewController, duration: animator.duration, options: [], animations: {// 执行转场动画animator.animateTransition(using: transitionContext)}, completion: { finished in// 从导航栈中移除被弹出的视图控制器self.navigationStack.pop()// 完成转场动画self.completeTransition(transitionContext, animated: true)})}private func performNonAnimatedPop(from fromViewController: UIViewController, to toViewController: UIViewController) {// 从导航栈中移除被弹出的视图控制器navigationStack.pop()// 准备显示新的视图控制器toViewController.willMove(toParent: self)toViewController.view.frame = view.boundsview.addSubview(toViewController.view)toViewController.didMove(toParent: self)// 移除被弹出的视图控制器fromViewController.willMove(toParent: nil)fromViewController.view.removeFromSuperview()fromViewController.removeFromParent()}
}

III. UINavigationController 的转场动画机制

UINavigationController 的转场动画是其核心功能之一。本章将深入分析转场动画的实现原理和机制。

3.1 转场动画的基本概念

转场动画是指在两个视图控制器之间切换时的动画效果。UINavigationController 提供了默认的水平滑动动画,也支持自定义转场动画。

转场动画涉及以下几个核心概念:

  • 转场动画控制器(Transition Animator):实现 UIViewControllerAnimatedTransitioning 协议,负责定义动画的具体实现。
  • 交互控制器(Interaction Controller):实现 UIViewControllerInteractiveTransitioning 协议,负责处理交互式转场。
  • 转场上下文(Transition Context):实现 UIViewControllerContextTransitioning 协议,提供转场过程中的上下文信息。
  • 导航控制器代理(Navigation Controller Delegate):实现 UINavigationControllerDelegate 协议,负责提供转场动画控制器和交互控制器。
3.2 默认转场动画的实现

UINavigationController 的默认转场动画是水平滑动效果。下面是其基本实现原理:

// 默认转场动画的实现(简化版)
class DefaultNavigationTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {private let isPush: Boolinit(isPush: Bool) {self.isPush = isPushsuper.init()}// 转场动画持续时间func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {return 0.3}// 执行转场动画func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {// 获取转场的源视图控制器和目标视图控制器guard let fromVC = transitionContext.viewController(forKey: .from),let toVC = transitionContext.viewController(forKey: .to),let containerView = transitionContext.containerView else {return}// 获取容器视图的边界let containerBounds = containerView.bounds// 设置目标视图的初始位置if isPush {toVC.view.frame = CGRect(x: containerBounds.width, y: 0, width: containerBounds.width, height: containerBounds.height)} else {toVC.view.frame = CGRect(x: -containerBounds.width, y: 0, width: containerBounds.width, height: containerBounds.height)}// 将目标视图添加到容器视图containerView.addSubview(toVC.view)// 执行动画UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0.8, options: [.curveEaseInOut], animations: {if self.isPush {fromVC.view.frame = CGRect(x: -containerBounds.width, y: 0, width: containerBounds.width, height: containerBounds.height)toVC.view.frame = containerBounds} else {fromVC.view.frame = CGRect(x: containerBounds.width, y: 0, width: containerBounds.width, height: containerBounds.height)toVC.view.frame = containerBounds}}) { finished in// 标记转场完成let wasCancelled = transitionContext.transitionWasCancelledtransitionContext.completeTransition(!wasCancelled)// 如果转场被取消,移除目标视图if wasCancelled {toVC.view.removeFromSuperview()}}}
}
3.3 自定义转场动画的实现

通过实现 UINavigationControllerDelegate 协议,可以自定义 UINavigationController 的转场动画。

// 自定义转场动画的实现(简化版)
class CustomNavigationDelegate: NSObject, UINavigationControllerDelegate {// 返回转场动画控制器func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {// 根据操作类型返回不同的动画控制器switch operation {case .push:return CustomPushAnimator()case .pop:return CustomPopAnimator()case .none:return nil@unknown default:return nil}}// 返回交互控制器func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {// 如果有交互转场,返回交互控制器// 否则返回 nil,表示使用非交互式转场return nil}
}// 自定义 push 动画控制器
class CustomPushAnimator: NSObject, UIViewControllerAnimatedTransitioning {func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {return 0.5}func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {// 获取源视图控制器和目标视图控制器guard let fromVC = transitionContext.viewController(forKey: .from),let toVC = transitionContext.viewController(forKey: .to),let containerView = transitionContext.containerView else {return}// 创建自定义动画效果// ...// 执行动画UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {// 动画内容// ...}) { finished in// 完成转场transitionContext.completeTransition(!transitionContext.transitionWasCancelled)}}
}// 自定义 pop 动画控制器
class CustomPopAnimator: NSObject, UIViewControllerAnimatedTransitioning {func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {return 0.5}func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {// 获取源视图控制器和目标视图控制器guard let fromVC = transitionContext.viewController(forKey: .from),let toVC = transitionContext.viewController(forKey: .to),let containerView = transitionContext.containerView else {return}// 创建自定义动画效果// ...// 执行动画UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {// 动画内容// ...}) { finished in// 完成转场transitionContext.completeTransition(!transitionContext.transitionWasCancelled)}}
}

IV. 导航栏与导航栈的交互

导航栏是 UINavigationController 的重要组成部分,它与导航栈密切交互。本章将深入分析导航栏与导航栈的交互机制。

4.1 导航栏的基本结构

导航栏(UINavigationBar)是一个专门用于显示导航信息的视图,通常位于屏幕顶部。它由以下几个部分组成:

  • 标题视图(Title View):显示当前视图控制器的标题。
  • 左按钮(Left Bar Button Item):通常用于返回上一级视图。
  • 右按钮(Right Bar Button Item):通常用于执行与当前视图相关的操作。
  • 工具栏(Toolbar):可选的底部工具栏,用于显示额外的导航按钮。
// UINavigationBar 的基本结构(简化版)
@objc open class UINavigationBar : UIView, UIBarPositioning {// 导航栏样式open var barStyle: UIBarStyle// 背景颜色open override var backgroundColor: UIColor?// 是否透明open var isTranslucent: Bool// 当前显示的导航项数组open var items: [UINavigationItem]?// 代理open weak var delegate: UINavigationBarDelegate?// 设置导航项open func setItems(_ items: [UINavigationItem]?, animated: Bool)// 添加导航项open func pushItem(_ item: UINavigationItem, animated: Bool)// 移除顶部导航项open func popItem(animated: Bool) -> UINavigationItem?// 获取顶部导航项open var topItem: UINavigationItem? { get }// 获取上一个导航项open var backItem: UINavigationItem? { get }
}// UINavigationItem 的基本结构(简化版)
@objc open class UINavigationItem : NSObject {// 标题open var title: String?// 自定义标题视图open var titleView: UIView?// 左按钮open var leftBarButtonItem: UIBarButtonItem?open var leftBarButtonItems: [UIBarButtonItem]?// 右按钮open var rightBarButtonItem: UIBarButtonItem?open var rightBarButtonItems: [UIBarButtonItem]?// 返回按钮open var backBarButtonItem: UIBarButtonItem?// 是否隐藏返回按钮open var hidesBackButton: Bool// 初始化方法public init(title: String)
}
4.2 导航栏与导航栈的同步机制

导航栏会根据导航栈的变化自动更新其内容。当视图控制器被推入或弹出导航栈时,导航栏会相应地更新其显示的导航项。

// 导航栏与导航栈的同步机制(简化版)
extension UINavigationController {// 更新导航栏内容private func updateNavigationBar(for viewController: UIViewController) {// 获取导航栏let navigationBar = self.navigationBar// 获取当前导航栈中的所有视图控制器let viewControllers = self.viewControllers// 创建对应的导航项数组let navigationItems = viewControllers.map { vc -> UINavigationItem in// 如果视图控制器没有自定义导航项,创建一个新的if vc.navigationItem.title == nil && vc.navigationItem.titleView == nil {let item = UINavigationItem(title: vc.title ?? "")item.leftBarButtonItem = vc.navigationItem.leftBarButtonItemitem.rightBarButtonItem = vc.navigationItem.rightBarButtonItemreturn item}return vc.navigationItem}// 设置导航栏的导航项navigationBar.setItems(navigationItems, animated: isBeingPresented || isBeingDismissed || viewControllers.count <= 1 ? false : true)// 更新导航栏的可见性updateNavigationBarVisibility()}// 更新导航栏的可见性private func updateNavigationBarVisibility() {// 获取当前可见的视图控制器guard let visibleViewController = visibleViewController else { return }// 检查视图控制器是否希望隐藏导航栏let shouldHide = visibleViewController.hidesBottomBarWhenPushed// 设置导航栏的可见性setNavigationBarHidden(shouldHide, animated: true)}// 设置导航栏的可见性open func setNavigationBarHidden(_ hidden: Bool, animated: Bool) {// 如果可见性没有变化,直接返回if hidden == navigationBar.isHidden {return}// 如果需要动画if animated {// 创建动画UIView.animate(withDuration: 0.3, animations: {// 调整导航栏的位置self.navigationBar.frame.origin.y = hidden ? -self.navigationBar.frame.height : 0// 调整内容视图的位置self.adjustContentViewForNavigationBarHidden(hidden)}) { finished in// 更新导航栏的可见性状态self.navigationBar.isHidden = hidden}} else {// 直接设置导航栏的可见性navigationBar.isHidden = hidden// 调整内容视图的位置adjustContentViewForNavigationBarHidden(hidden)}}// 调整内容视图的位置private func adjustContentViewForNavigationBarHidden(_ hidden: Bool) {// 获取内容视图guard let contentView = view.subviews.first(where: { $0 !== navigationBar && $0 !== toolbar }) else { return }// 调整内容视图的 framevar contentFrame = view.boundsif !hidden {contentFrame.origin.y = navigationBar.frame.heightcontentFrame.size.height -= navigationBar.frame.height}if !isToolbarHidden {contentFrame.size.height -= toolbar.frame.height}contentView.frame = contentFrame}
}
4.3 导航栏按钮的动态更新

导航栏的按钮可以根据视图控制器的状态动态更新。

// 导航栏按钮的动态更新(简化版)
extension UIViewController {// 更新导航栏按钮func updateNavigationBarButtons() {// 检查是否是导航栈的根视图控制器if navigationController?.viewControllers.first === self {// 根视图控制器通常不需要返回按钮navigationItem.hidesBackButton = true// 设置其他按钮setupRootViewControllerButtons()} else {// 非根视图控制器通常需要返回按钮navigationItem.hidesBackButton = false// 设置其他按钮setupNonRootViewControllerButtons()}}// 设置根视图控制器的按钮private func setupRootViewControllerButtons() {// 创建右按钮let rightButton = UIBarButtonItem(title: "设置", style: .plain, target: self, action: #selector(settingsButtonTapped))// 设置右按钮navigationItem.rightBarButtonItem = rightButton}// 设置非根视图控制器的按钮private func setupNonRootViewControllerButtons() {// 检查是否需要自定义返回按钮if shouldCustomizeBackButton {// 创建自定义返回按钮let backButton = UIBarButtonItem(title: "返回", style: .plain, target: self, action: #selector(backButtonTapped))// 设置返回按钮navigationItem.leftBarButtonItem = backButton}// 创建右按钮let rightButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneButtonTapped))// 设置右按钮navigationItem.rightBarButtonItem = rightButton}// 返回按钮点击事件@objc func backButtonTapped() {// 执行返回操作navigationController?.popViewController(animated: true)}// 设置按钮点击事件@objc func settingsButtonTapped() {// 执行设置操作}// 完成按钮点击事件@objc func doneButtonTapped() {// 执行完成操作}// 是否应该自定义返回按钮var shouldCustomizeBackButton: Bool {return false}
}

V. 交互式转场动画

UINavigationController 支持交互式转场动画,允许用户通过手势等方式控制转场过程。本章将深入分析交互式转场动画的实现原理。

5.1 交互式转场的基本概念

交互式转场允许用户通过手势等方式控制转场动画的进度,例如通过滑动屏幕来返回上一级视图。交互式转场涉及以下几个核心组件:

  • 交互控制器(Interaction Controller):实现 UIViewControllerInteractiveTransitioning 协议,负责处理用户交互并控制转场动画的进度。
  • 交互代理(Interaction Delegate):通常是视图控制器或导航控制器的代理,负责提供交互控制器。
  • 手势识别器(Gesture Recognizer):用于检测用户的交互动作,如滑动手势。
5.2 交互式转场的实现流程

实现交互式转场通常需要以下步骤:

  1. 创建一个实现 UIViewControllerInteractiveTransitioning 协议的交互控制器。
  2. 在导航控制器的代理中实现 navigationController(_:interactionControllerFor:) 方法,返回交互控制器。
  3. 在视图控制器中添加手势识别器,并在手势处理方法中更新交互控制器的进度。
// 交互式转场的实现(简化版)
class InteractivePopTransition: UIPercentDrivenInteractiveTransition {private var navigationController: UINavigationController?private var isInteracting = falseprivate var shouldCompleteTransition = falseinit(navigationController: UINavigationController) {self.navigationController = navigationControllersuper.init()setupGestureRecognizer()}// 设置手势识别器private func setupGestureRecognizer() {guard let navigationController = navigationController else { return }// 创建滑动手势识别器let gesture = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleGesture(_:)))gesture.edges = .leftnavigationController.view.addGestureRecognizer(gesture)}// 处理手势@objc private func handleGesture(_ gesture: UIScreenEdgePanGestureRecognizer) {guard let navigationController = navigationController else { return }// 计算手势进度let translation = gesture.translation(in: gesture.view?.superview)var progress = translation.x / (gesture.view?.bounds.width ?? 300)progress = min(max(progress, 0.0), 1.0)switch gesture.state {case .began:// 开始交互转场isInteracting = truenavigationController.popViewController(animated: true)case .changed:// 更新转场进度shouldCompleteTransition = progress > 0.5update(progress)case .cancelled, .ended:// 结束交互转场isInteracting = falseif shouldCompleteTransition || gesture.state == .ended {finish()} else {cancel()}default:break}}// 判断是否正在进行交互式转场func isInteractive() -> Bool {return isInteracting}
}// 导航控制器代理实现
class NavigationControllerDelegate: NSObject, UINavigationControllerDelegate {private var interactiveTransition: InteractivePopTransition?func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {// 返回自定义动画控制器return DefaultNavigationTransitionAnimator(isPush: operation == .push)}func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {// 如果是 pop 操作并且有交互式转场,返回交互控制器if let interactiveTransition = interactiveTransition, interactiveTransition.isInteractive() {return interactiveTransition}return nil}func setupInteractiveTransition(for navigationController: UINavigationController) {// 创建交互式转场控制器interactiveTransition = InteractivePopTransition(navigationController: navigationController)}
}
5.3 交互式转场的性能优化

交互式转场可能会对性能产生影响,因此需要进行适当的优化。

// 交互式转场的性能优化(简化版)
extension InteractivePopTransition {// 优化性能的方法func optimizePerformance() {// 禁用隐式动画CATransaction.begin()CATransaction.setDisableActions(true)// 优化视图层属性if let navigationController = navigationController,let fromView = navigationController.topViewController?.view,let toView = navigationController.viewControllers.count > 1 ? navigationController.viewControllers[navigationController.viewControllers.count - 2].view : nil {// 启用光栅化以提高性能fromView.layer.shouldRasterize = truefromView.layer.rasterizationScale = UIScreen.main.scaleif let toView = toView {toView.layer.shouldRasterize = truetoView.layer.rasterizationScale = UIScreen.main.scale}}CATransaction.commit()}// 清理性能优化func cleanupPerformanceOptimizations() {if let navigationController = navigationController,let fromView = navigationController.topViewController?.view,let toView = navigationController.viewControllers.count > 1 ? navigationController.viewControllers[navigationController.viewControllers.count - 2].view : nil {// 禁用光栅化fromView.layer.shouldRasterize = falseif let toView = toView {toView.layer.shouldRasterize = false}}}
}

VI. 自定义转场动画的高级技巧

除了基本的自定义转场动画外,还有一些高级技巧可以实现更复杂的效果。本章将深入分析这些高级技巧。

6.1 使用转场容器视图

转场容器视图(transition container view)是转场动画的核心,它提供了转场过程中视图控制器视图的容器。

// 使用转场容器视图的高级技巧(简化版)
class AdvancedTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {return 0.5}func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {// 获取容器视图guard let containerView = transitionContext.containerView else { return }// 获取源视图控制器和目标视图控制器guard let fromVC = transitionContext.viewController(forKey: .from),let toVC = transitionContext.viewController(forKey: .to) else {transitionContext.completeTransition(false)return}// 获取源视图和目标视图let fromView = fromVC.view!let toView = toVC.view!// 配置目标视图的初始状态toView.frame = transitionContext.finalFrame(for: toVC)toView.alpha = 0.0// 将目标视图添加到容器视图containerView.addSubview(toView)// 创建自定义动画效果// 例如:使用容器视图创建复杂的动画效果let snapshot = fromView.snapshotView(afterScreenUpdates: false)if let snapshot = snapshot {containerView.addSubview(snapshot)fromView.isHidden = true// 对快照视图执行动画UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {snapshot.alpha = 0.0snapshot.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)toView.alpha = 1.0}) { finished in// 清理snapshot.removeFromSuperview()fromView.isHidden = false// 完成转场transitionContext.complete

VI. 自定义转场动画的高级技巧(续)

6.2 使用CATransition实现转场动画

除了使用UIView动画,还可以使用Core Animation的CATransition来实现更复杂的转场效果。

// 使用CATransition实现转场动画(简化版)
class CATransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {return 0.5}func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {// 获取容器视图guard let containerView = transitionContext.containerView else { return }// 获取源视图控制器和目标视图控制器guard let fromVC = transitionContext.viewController(forKey: .from),let toVC = transitionContext.viewController(forKey: .to) else {transitionContext.completeTransition(false)return}// 获取源视图和目标视图let fromView = fromVC.view!let toView = toVC.view!// 将目标视图添加到容器视图containerView.addSubview(toView)// 创建CATransitionlet transition = CATransition()transition.duration = transitionDuration(using: transitionContext)transition.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)// 设置转场类型和子类型transition.type = .pushtransition.subtype = .fromRight// 添加转场动画到容器视图的layercontainerView.layer.add(transition, forKey: kCATransition)// 完成转场DispatchQueue.main.async {transitionContext.completeTransition(!transitionContext.transitionWasCancelled)}}
}
6.3 使用3D变换实现立体转场效果

通过组合多个动画和3D变换,可以实现复杂的立体转场效果。

// 使用3D变换实现立体转场效果(简化版)
class CubeTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {return 0.6}func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {// 获取容器视图guard let containerView = transitionContext.containerView else { return }// 获取源视图控制器和目标视图控制器guard let fromVC = transitionContext.viewController(forKey: .from),let toVC = transitionContext.viewController(forKey: .to) else {transitionContext.completeTransition(false)return}// 获取源视图和目标视图let fromView = fromVC.view!let toView = toVC.view!// 设置3D透视效果var perspective = CATransform3DIdentityperspective.m34 = -1.0 / 500.0containerView.layer.sublayerTransform = perspective// 获取容器视图的尺寸let containerSize = containerView.bounds.size// 设置源视图和目标视图的初始位置和变换toView.frame = CGRect(x: containerSize.width, y: 0, width: containerSize.width, height: containerSize.height)toView.layer.transform = CATransform3DMakeRotation(CGFloat.pi / 2, 0, 1, 0)// 将源视图和目标视图添加到容器视图containerView.addSubview(toView)containerView.addSubview(fromView)// 执行3D旋转动画UIView.animateKeyframes(withDuration: transitionDuration(using: transitionContext), delay: 0, options: [], animations: {UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 1.0) {fromView.layer.transform = CATransform3DMakeRotation(-CGFloat.pi / 2, 0, 1, 0)toView.layer.transform = CATransform3DIdentity}}) { finished in// 重置3D透视效果containerView.layer.sublayerTransform = CATransform3DIdentity// 完成转场transitionContext.completeTransition(!transitionContext.transitionWasCancelled)}}
}
6.4 实现转场动画的中断和恢复

在某些情况下,可能需要暂停、恢复或中断正在进行的转场动画。

// 实现转场动画的中断和恢复(简化版)
class InterruptibleTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {private var animator: UIViewPropertyAnimator?func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {return 0.5}func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {// 获取容器视图guard let containerView = transitionContext.containerView else { return }// 获取源视图控制器和目标视图控制器guard let fromVC = transitionContext.viewController(forKey: .from),let toVC = transitionContext.viewController(forKey: .to) else {transitionContext.completeTransition(false)return}// 获取源视图和目标视图let fromView = fromVC.view!let toView = toVC.view!// 配置目标视图的初始状态toView.frame = transitionContext.finalFrame(for: toVC)toView.alpha = 0.0// 将目标视图添加到容器视图containerView.addSubview(toView)// 创建可中断的动画animator = UIViewPropertyAnimator(duration: transitionDuration(using: transitionContext), curve: .easeInOut) {fromView.alpha = 0.0toView.alpha = 1.0}animator?.addCompletion { position inlet wasCancelled = transitionContext.transitionWasCancelledif position == .end && !wasCancelled {fromView.removeFromSuperview()}transitionContext.completeTransition(!wasCancelled)}// 开始动画animator?.startAnimation()}// 暂停动画func pauseAnimation() {animator?.pauseAnimation()}// 继续动画func continueAnimation() {animator?.continueAnimation(withTimingParameters: nil, durationFactor: 0)}// 取消动画func cancelAnimation() {animator?.stopAnimation(true)}
}

VII. UINavigationController 的内存管理

UINavigationController 的内存管理是开发中的重要问题。本章将深入分析 UINavigationController 的内存管理机制。

7.1 视图控制器的生命周期与内存管理

视图控制器的生命周期直接影响内存管理。当视图控制器被推入导航栈时,会被保留;当被弹出时,会被释放。

// 视图控制器的生命周期与内存管理(简化版)
extension UIViewController {// 视图控制器被加载到内存时调用override open func loadView() {super.loadView()// 初始化视图层次结构setupView()}// 视图控制器被添加到导航栈时调用override open func viewWillAppear(_ animated: Bool) {super.viewWillAppear(animated)// 准备视图即将显示prepareForAppearance()}// 视图控制器的视图已经显示时调用override open func viewDidAppear(_ animated: Bool) {super.viewDidAppear(animated)// 视图已经显示,可以开始动画或其他操作startAnimations()}// 视图控制器的视图即将消失时调用override open func viewWillDisappear(_ animated: Bool) {super.viewWillDisappear(animated)// 准备视图即将消失prepareForDisappearance()}// 视图控制器的视图已经消失时调用override open func viewDidDisappear(_ animated: Bool) {super.viewDidDisappear(animated)// 视图已经消失,可以停止动画或其他操作stopAnimations()// 检查是否被从导航栈中移除if isBeingRemoved {// 释放资源releaseResources()}}// 视图控制器被释放时调用deinit {// 清理资源cleanupResources()}// 释放资源的方法private func releaseResources() {// 释放大对象largeData = nil// 取消网络请求networkTask?.cancel()networkTask = nil// 停止计时器timer?.invalidate()timer = nil}
}
7.2 避免内存泄漏的最佳实践

在使用 UINavigationController 时,需要注意避免内存泄漏。

// 避免内存泄漏的最佳实践(简化版)
class MemorySafeViewController: UIViewController {private var timer: Timer?private var networkTask: URLSessionDataTask?private var largeData: Data?override func viewDidLoad() {super.viewDidLoad()// 弱引用 self,避免循环引用timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(timerFired), userInfo: nil, repeats: true)// 使用闭包时弱引用 selffetchData { [weak self] data inself?.processData(data)}}override func viewDidDisappear(_ animated: Bool) {super.viewDidDisappear(animated)// 视图消失时释放资源timer?.invalidate()timer = nilnetworkTask?.cancel()networkTask = nil}deinit {print("MemorySafeViewController deinitialized")}@objc private func timerFired() {// 计时器回调}private func fetchData(completion: @escaping (Data) -> Void) {let url = URL(string: "https://example.com/data")!networkTask = URLSession.shared.dataTask(with: url) { data, response, error inif let data = data {completion(data)}}networkTask?.resume()}private func processData(_ data: Data) {// 处理数据largeData = data}
}
7.3 处理大内存对象的策略

当视图控制器包含大内存对象时,需要特别注意内存管理。

// 处理大内存对象的策略(简化版)
class LargeMemoryViewController: UIViewController {private var image: UIImage?private var data: Data?override func viewDidLoad() {super.viewDidLoad()// 视图加载时不立即加载大内存对象}override func viewWillAppear(_ animated: Bool) {super.viewWillAppear(animated)// 视图即将显示时加载大内存对象loadLargeData()}override func viewDidDisappear(_ animated: Bool) {super.viewDidDisappear(animated)// 视图消失时释放大内存对象releaseLargeData()}private func loadLargeData() {// 从磁盘或网络加载大内存对象if let imagePath = Bundle.main.path(forResource: "largeImage", ofType: "jpg") {image = UIImage(contentsOfFile: imagePath)}if let dataPath = Bundle.main.path(forResource: "largeData", ofType: "dat") {do {data = try Data(contentsOf: URL(fileURLWithPath: dataPath))} catch {print("Error loading data: \(error)")}}}private func releaseLargeData() {// 释放大内存对象image = nildata = nil// 触发内存警告,提示系统回收内存performMemoryWarning()}private func performMemoryWarning() {// 模拟内存警告,仅用于测试#if DEBUGNotificationCenter.default.post(name: UIApplication.didReceiveMemoryWarningNotification, object: nil)#endif}
}

VIII. UINavigationController 的性能优化

UINavigationController 的性能优化是提升应用体验的关键。本章将深入分析 UINavigationController 的性能优化技巧。

8.1 转场动画的性能优化

转场动画可能会对性能产生影响,需要进行优化。

// 转场动画的性能优化(简化版)
class PerformanceOptimizedAnimator: NSObject, UIViewControllerAnimatedTransitioning {func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {return 0.3 // 保持动画时间较短}func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {// 获取容器视图guard let containerView = transitionContext.containerView else { return }// 获取源视图控制器和目标视图控制器guard let fromVC = transitionContext.viewController(forKey: .from),let toVC = transitionContext.viewController(forKey: .to) else {transitionContext.completeTransition(false)return}// 获取源视图和目标视图let fromView = fromVC.view!let toView = toVC.view!// 优化性能的关键设置fromView.layer.shouldRasterize = truefromView.layer.rasterizationScale = UIScreen.main.scaletoView.layer.shouldRasterize = truetoView.layer.rasterizationScale = UIScreen.main.scale// 将目标视图添加到容器视图containerView.addSubview(toView)// 执行动画UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {// 简单的平移动画,避免复杂的变换fromView.frame.origin.x = -fromView.bounds.widthtoView.frame.origin.x = 0}) { finished in// 重置优化设置fromView.layer.shouldRasterize = falsetoView.layer.shouldRasterize = false// 完成转场transitionContext.completeTransition(!transitionContext.transitionWasCancelled)}}
}
8.2 视图控制器懒加载策略

对于不立即需要的视图控制器,可以采用懒加载策略。

// 视图控制器懒加载策略(简化版)
class LazyLoadingNavigationController: UINavigationController {private var lazyViewControllers: [String: () -> UIViewController] = [:]private var loadedViewControllers: [String: UIViewController] = [:]// 注册懒加载的视图控制器func registerLazyViewController(_ identifier: String, factory: @escaping () -> UIViewController) {lazyViewControllers[identifier] = factory}// 获取懒加载的视图控制器func getViewController(_ identifier: String) -> UIViewController {if let viewController = loadedViewControllers[identifier] {return viewController}if let factory = lazyViewControllers[identifier] {let viewController = factory()loadedViewControllers[identifier] = viewControllerreturn viewController}fatalError("No view controller registered for identifier: \(identifier)")}// 懒加载并推入视图控制器func pushLazyViewController(_ identifier: String, animated: Bool = true) {let viewController = getViewController(identifier)pushViewController(viewController, animated: animated)}
}
8.3 预加载与缓存策略

对于频繁使用的视图控制器,可以采用预加载和缓存策略。

// 预加载与缓存策略(简化版)
class CachingNavigationController: UINavigationController {private var cachedViewControllers: [String: UIViewController] = [:]private var preloadQueue: OperationQueue = {let queue = OperationQueue()queue.maxConcurrentOperationCount = 2return queue}()// 预加载视图控制器func preloadViewController(_ identifier: String, factory: @escaping () -> UIViewController) {let operation = BlockOperation { [weak self] inlet viewController = factory()self?.cachedViewControllers[identifier] = viewController}preloadQueue.addOperation(operation)}// 获取缓存的视图控制器func getCachedViewController(_ identifier: String, factory: @escaping () -> UIViewController) -> UIViewController {if let viewController = cachedViewControllers[identifier] {return viewController}let viewController = factory()cachedViewControllers[identifier] = viewControllerreturn viewController}// 推入缓存的视图控制器func pushCachedViewController(_ identifier: String, factory: @escaping () -> UIViewController, animated: Bool = true) {let viewController = getCachedViewController(identifier, factory: factory)pushViewController(viewController, animated: animated)}// 清理缓存func clearCache() {cachedViewControllers.removeAll()}
}

IX. UINavigationController 的常见问题与解决方案

在使用 UINavigationController 时,可能会遇到各种问题。本章将深入分析常见问题及其解决方案。

9.1 导航栏闪烁问题

导航栏闪烁是一个常见问题,通常发生在导航栏隐藏或显示时。

// 解决导航栏闪烁问题(简化版)
extension UINavigationController {// 平滑切换导航栏的可见性func setNavigationBarHiddenSmoothly(_ hidden: Bool, animated: Bool) {// 如果可见性没有变化,直接返回if hidden == navigationBar.isHidden {return}// 如果不需要动画,直接设置if !animated {setNavigationBarHidden(hidden, animated: false)return}// 创建快照视图let snapshotView = navigationBar.snapshotView(afterScreenUpdates: true)snapshotView?.frame = navigationBar.frameif let snapshotView = snapshotView {// 添加快照视图view.addSubview(snapshotView)// 设置导航栏隐藏状态setNavigationBarHidden(hidden, animated: false)// 执行动画UIView.animate(withDuration: 0.3, animations: {if hidden {snapshotView.alpha = 0.0snapshotView.frame.origin.y = -snapshotView.frame.height} else {snapshotView.alpha = 1.0snapshotView.frame.origin.y = 0}}) { finished in// 移除快照视图snapshotView.removeFromSuperview()}} else {// 如果无法创建快照视图,使用默认方法setNavigationBarHidden(hidden, animated: true)}}
}
9.2 转场动画卡顿问题

转场动画卡顿通常是由于动画复杂度高或性能优化不足导致的。

// 解决转场动画卡顿问题(简化版)
class SmoothTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {return 0.3 // 保持动画时间较短}func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {// 获取容器视图guard let containerView = transitionContext.containerView else { return }// 获取源视图控制器和目标视图控制器guard let fromVC = transitionContext.viewController(forKey: .from),let toVC = transitionContext.viewController(forKey: .to) else {transitionContext.completeTransition(false)return}// 获取源视图和目标视图let fromView = fromVC.view!let toView = toVC.view!// 优化性能的关键设置fromView.layer.shouldRasterize = truefromView.layer.rasterizationScale = UIScreen.main.scaletoView.layer.shouldRasterize = truetoView.layer.rasterizationScale = UIScreen.main.scale// 将目标视图添加到容器视图containerView.addSubview(toView)// 异步执行动画DispatchQueue.global(qos: .userInteractive).async {DispatchQueue.main.async {UIView.animate(withDuration: self.transitionDuration(using: transitionContext), animations: {// 简单的平移动画fromView.frame.origin.x = -fromView.bounds.widthtoView.frame.origin.x = 0}) { finished in// 重置优化设置fromView.layer.shouldRasterize = falsetoView.layer.shouldRasterize = false// 完成转场transitionContext.completeTransition(!transitionContext.transitionWasCancelled)}}}}
}
9.3 导航栈管理错误

不正确的导航栈管理可能导致应用崩溃或行为异常。

// 解决导航栈管理错误(简化版)
extension UINavigationController {// 安全地推入视图控制器func safelyPushViewController(_ viewController: UIViewController, animated: Bool) {// 检查视图控制器是否已经在栈中if viewControllers.contains(where: { $0 === viewController }) {// 如果已经在栈中,先弹出到该视图控制器popToViewController(viewController, animated: animated)} else {// 否则正常推入pushViewController(viewController, animated: animated)}}// 安全地弹出到根视图控制器func safelyPopToRootViewController(animated: Bool) {// 检查栈中是否有多个视图控制器if viewControllers.count > 1 {popToRootViewController(animated: animated)}}// 安全地弹出视图控制器func safelyPopViewController(animated: Bool) {// 检查栈中是否有多个视图控制器if viewControllers.count > 1 {popViewController(animated: animated)}}// 安全地弹出到指定视图控制器func safelyPopToViewController(_ viewController: UIViewController, animated: Bool) -> [UIViewController]? {// 检查目标视图控制器是否在栈中if viewControllers.contains(where: { $0 === viewController }) {return popToViewController(viewController, animated: animated)}return nil}
}

X. UINavigationController 在不同iOS版本中的变化

UINavigationController 在不同的 iOS 版本中可能会有行为上的变化。本章将深入分析这些变化及其影响。

10.1 iOS 11 中的变化

iOS 11 对 UINavigationController 进行了一些重要更新。

// iOS 11 中的 UINavigationController 变化(简化版)
extension UINavigationController {// 适配 iOS 11 的安全区域func setupForiOS11() {if #available(iOS 11.0, *) {// 启用大标题navigationBar.prefersLargeTitles = true// 设置大标题样式navigationBar.largeTitleTextAttributes = [.foregroundColor: UIColor.black,.font: UIFont.systemFont(ofSize: 34, weight: .bold)]// 调整内容 inset 行为if let scrollView = view.subviews.first(where: { $0 is UIScrollView }) as? UIScrollView {scrollView.contentInsetAdjustmentBehavior = .automatic}}}// 适配 iOS 11 的搜索控制器func setupSearchController(_ searchController: UISearchController) {if #available(iOS 11.0, *) {// 设置导航栏的搜索控制器navigationBar.searchController = searchController// 设置搜索控制器的显示方式navigationItem.hidesSearchBarWhenScrolling = true} else {// 在 iOS 10 及以下版本中,需要手动添加搜索栏// ...}}
}
10.2 iOS 13 中的变化

iOS 13 引入了一些新的导航行为和 API。

// iOS 13 中的 UINavigationController 变化(简化版)
extension UINavigationController {// 适配 iOS 13 的深色模式func setupForDarkMode() {if #available(iOS 13.0, *) {// 创建导航栏外观let appearance = UINavigationBarAppearance()// 设置背景颜色appearance.backgroundColor = .systemBackground// 设置标题文本属性appearance.titleTextAttributes = [.foregroundColor: UIColor.label]// 设置大标题文本属性appearance.largeTitleTextAttributes = [.foregroundColor: UIColor.label]// 设置导航栏的标准外观navigationBar.standardAppearance = appearance// 设置导航栏的滚动时外观navigationBar.scrollEdgeAppearance = appearance// 设置工具栏外观if let toolbar = toolbar {let toolbarAppearance = UIToolbarAppearance()toolbarAppearance.configureWithDefaultBackground()toolbar.standardAppearance = toolbarAppearancetoolbar.scrollEdgeAppearance = toolbarAppearance}}}// 使用 iOS 13 的新转场风格func useModernTransitionStyle() {if #available(iOS 13.0, *) {// 设置为卡片式转场modalPresentationStyle = .pageSheet// 允许交互式 dismissalisModalInPresentation = false}}
}
10.3 iOS 15 中的变化

iOS 15 对 UINavigationController 进行了进一步的改进。

// iOS 15 中的 UINavigationController 变化(简化版)
extension UINavigationController {// 适配 iOS 15 的导航栏外观变化func setupForiOS15() {if #available(iOS 15.0, *) {// 创建导航栏外观let appearance = UINavigationBarAppearance()// 设置背景效果appearance.configureWithOpaqueBackground()// 设置背景颜色appearance.backgroundColor = .systemBackground// 设置阴影颜色appearance.shadowColor = .systemGray4// 设置导航栏的标准外观navigationBar.standardAppearance = appearance// 设置导航栏的滚动时外观navigationBar.scrollEdgeAppearance = appearance// 设置紧凑外观navigationBar.compactAppearance = appearance}}// 适配 iOS 15 的工具栏外观变化func setupToolbarForiOS15() {if #available(iOS 15.0, *) {// 创建工具栏外观let appearance = UIToolbarAppearance()// 设置背景效果appearance.configureWithOpaqueBackground()// 设置背景颜色appearance.backgroundColor = .systemBackground// 设置阴影颜色appearance.shadowColor = .systemGray4// 设置工具栏的标准外观toolbar.standardAppearance = appearance// 设置工具栏的滚动时外观toolbar.scrollEdgeAppearance = appearance}}
}

XI. UINavigationController 的替代方案与扩展

除了标准的 UINavigationController,还有一些替代方案和扩展可以满足特定需求。本章将深入分析这些替代方案和扩展。

11.1 使用自定义容器控制器实现导航功能

可以通过自定义容器控制器来实现类似 UINavigationController 的功能。

// 自定义导航容器控制器(简化版)
class CustomNavigationController: UIViewController {private var viewControllers: [UIViewController] = []private var currentViewController: UIViewController?override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)commonInit()}required init?(coder: NSCoder) {super.init(coder: coder)commonInit()}private func commonInit() {// 设置为不透明背景view.backgroundColor = .systemBackground}// 设置根视图控制器func setRootViewController(_ viewController: UIViewController) {// 清除现有视图控制器removeAllChildViewControllers()// 添加新的根视图控制器viewControllers = [viewController]addChild(viewController)view.addSubview(viewController.view)viewController.view.frame = view.boundsviewController.didMove(toParent: self)currentViewController = viewController}// 推入视图控制器func pushViewController(_ viewController: UIViewController, animated: Bool = true) {guard let currentViewController = currentViewController else {setRootViewController(viewController)return}// 添加新的视图控制器viewControllers.append(viewController)addChild(viewController)// 设置新视图控制器的初始位置viewController.view.frame = CGRect(x: view.bounds.width, y: 0, width: view.bounds.width, height: view.bounds.height)view.addSubview(viewController.view)viewController.didMove(toParent: self)// 执行转场动画if animated {UIView.animate(withDuration: 0.3, animations: {currentViewController.view.frame = CGRect(x: -self.view.bounds.width, y: 0, width: self.view.bounds.width, height: self.view.bounds.height)viewController.view.frame = self.view.bounds}) { finished in// 移除当前视图控制器currentViewController.willMove(toParent: nil)currentViewController.view.removeFromSuperview()currentViewController.removeFromParent()// 更新当前视图控制器self.currentViewController = viewController}} else {// 直接更新视图currentViewController.willMove(toParent: nil)currentViewController.view.removeFromSuperview()currentViewController.removeFromParent()// 更新当前视图控制器self.currentViewController = viewController}}// 弹出视图控制器func popViewController(animated: Bool = true) -> UIViewController? {guard viewControllers.count > 1 else { return nil }let poppedViewController = viewControllers.removeLast()let newCurrentViewController = viewControllers.last!// 添加新的当前视图控制器addChild(newCurrentViewController)newCurrentViewController.view.frame = CGRect(x: -view.bounds.width, y: 0, width: view.bounds.width, height: view.bounds.height)view.insertSubview(newCurrentViewController.view, at: 0)newCurrentViewController.didMove(toParent: self)// 执行转场动画if animated {UIView.animate(withDuration: 0.3, animations: {poppedViewController.view.frame = CGRect(x: self.view.bounds.width, y: 0, width: self.view.bounds.width, height: self.view.bounds.height)newCurrentViewController.view.frame = self.view.bounds}) { finished in// 移除弹出的视图控制器poppedViewController.willMove(toParent: nil)poppedViewController.view.removeFromSuperview()poppedViewController.removeFromParent()// 更新当前视图控制器self.currentViewController = newCurrentViewController}} else {// 直接更新视图poppedViewController.willMove(toParent: nil)poppedViewController.view.removeFromSuperview()poppedViewController.removeFromParent()// 更新当前视图控制器self.currentViewController = newCurrentViewController}return poppedViewController}// 移除所有子视图控制器private func removeAllChildViewControllers() {for child in children {child.willMove(toParent: nil)child.view.removeFromSuperview()child.removeFromParent()}}
}
11.2 使用第三方库扩展导航功能

有许多第三方库可以扩展 UINavigationController 的功能。

// 使用 RxSwift 扩展导航功能(简化版)
import RxSwift
import RxCocoaextension Reactive where Base: UINavigationController {// 可观察的导航栈变化var viewControllers: Observable<[UIViewController]> {return observe([UIViewController].self, "viewControllers").compactMap { $0 }}// 可观察的 push 操作var didPush: Observable<UIViewController> {return methodInvoked(#selector(UINavigationController.pushViewController(_:animated:))).map { parameters inreturn parameters[0] as! UIViewController}}// 可观察的 pop 操作var didPop: Observable<UIViewController> {return methodInvoked(#selector(UINavigationController.popViewController(animated:))).map { parameters inreturn parameters[0] as! UIViewController}}// 使用 RxSwift 实现的 push 操作func pushViewController(_ viewController: UIViewController, animated: Bool = true) -> Completable {return Completable.create { [weak base] observer inbase?.pushViewController(viewController, animated: animated)observer(.completed)return Disposables.create()}}// 使用 RxSwift 实现的 pop 操作func popViewController(animated: Bool = true) -> Completable {return Completable.create { [weak base] observer in_ = base?.popViewController(animated: animated)observer(.completed)return Disposables.create()}}
}
11.3 使用 SwiftUI 的 NavigationView

SwiftUI 提供了自己的导航组件 NavigationView。

// 使用 SwiftUI 的 NavigationView(简化版)
import SwiftUIstruct ContentView: View {var body: some View {NavigationView {List {NavigationLink(destination: DetailView()) {Text("项目 1")}NavigationLink(destination: DetailView()) {Text("项目 2")}}.navigationTitle("主界面").navigationBarTitleDisplayMode(.inline)}}
}struct DetailView: View {var body: some View {Text("详情页面").navigationTitle("详情").navigationBarTitleDisplayMode(.inline).navigationBarItems(trailing: Button("完成") {// 这里可以执行返回操作})}
}

XII. UINavigationController 的最佳实践

在使用 UINavigationController 时,遵循一些最佳实践可以提高代码质量和用户体验。本章将深入分析这些最佳实践。

12.1 导航设计的最佳实践

良好的导航设计是应用成功的关键。

// 导航设计的最佳实践(简化版)
extension UIViewController {// 设置标准的导航栏样式func setupStandardNavigationBarStyle() {// 设置导航栏标题navigationItem.title = title ?? "默认标题"// 设置导航栏背景颜色if #available(iOS 13.0, *) {let appearance = UINavigationBarAppearance()appearance.configureWithOpaqueBackground()appearance.backgroundColor = .systemBackgroundnavigationController?.navigationBar.standardAppearance = appearancenavigationController?.navigationBar.scrollEdgeAppearance = appearance} else {navigationController?.navigationBar.barTintColor = .whitenavigationController?.navigationBar.isTranslucent = false}// 设置导航栏标题颜色navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.label]}// 设置返回按钮样式func setupBackButtonStyle() {// 创建自定义返回按钮let backButton = UIBarButtonItem(title: "返回", style: .plain, target: nil, action: nil)// 设置返回按钮navigationItem.backBarButtonItem = backButton// 设置返回按钮颜色navigationController?.navigationBar.tintColor = .label}// 配置导航栏按钮func configureNavigationBarButtons() {// 设置左按钮if shouldShowLeftButton {let leftButton = UIBarButtonItem(image: UIImage(systemName: "menu"), style: .plain, target: self, action: #selector(leftButtonTapped))navigationItem.leftBarButtonItem = leftButton}// 设置右按钮if shouldShowRightButton {let rightButton = UIBarButtonItem(image: UIImage(systemName: "ellipsis"), style: .plain, target: self, action: #selector(rightButtonTapped))navigationItem.rightBarButtonItem = rightButton}}@objc private func leftButtonTapped() {// 处理左按钮点击事件}@objc private func rightButtonTapped() {// 处理右按钮点击事件}// 是否显示左按钮var shouldShowLeftButton: Bool {return false}// 是否显示右按钮var shouldShowRightButton: Bool {return false}
}
12.2 代码组织的最佳实践

良好的代码组织可以提高代码的可维护性。

// 代码组织的最佳实践(简化版)
// 1. 使用扩展分离关注点
extension MyViewController {// 视图设置private func setupViews() {// 设置主视图view.backgroundColor = .systemBackground// 设置子视图setupTableView()setupSearchBar()}// 设置表格视图private func setupTableView() {tableView.delegate = selftableView.dataSource = selftableView.register(MyCell.self, forCellReuseIdentifier: "cell")view.addSubview(tableView)// 设置约束tableView.translatesAutoresizingMaskIntoConstraints = falseNSLayoutConstraint.activate([tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)])}// 设置搜索栏private func setupSearchBar() {searchController.searchResultsUpdater = selfsearchController.obscuresBackgroundDuringPresentation = falsesearchController.searchBar.placeholder = "搜索..."navigationItem.searchController = searchControllerdefinesPresentationContext = true}
}// 2. 实现协议扩展
extension MyViewController: UITableViewDelegate, UITableViewDataSource {func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {return dataSource.count}func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyCellcell.configure(with: dataSource[indexPath.row])return cell}func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {tableView.deselectRow(at: indexPath, animated: true)// 导航到详情页面let detailVC = DetailViewController()detailVC.item = dataSource[indexPath.row]navigationController?.pushViewController(detailVC, animated: true)}
}// 3. 数据处理
extension MyViewController {// 加载数据func loadData() {// 显示加载指示器showLoadingIndicator()// 异步加载数据DispatchQueue.global().async {// 模拟数据加载Thread.sleep(forTimeInterval: 2.0)// 更新 UIDispatchQueue.main.async {// 隐藏加载指示器self.hideLoadingIndicator()// 更新数据self.dataSource = self.generateMockData()// 刷新表格self.tableView.reloadData()}}}// 生成模拟数据private func generateMockData() -> [Item] {// 生成模拟数据return [Item(title: "项目 1"), Item(title: "项目 2"), Item(title: "项目 3")]}
}
12.3 用户体验优化的最佳实践

优化用户体验可以提高应用的满意度。

// 用户体验优化的最佳实践(简化版)
extension UIViewController {// 显示加载指示器func showLoadingIndicator() {// 创建加载指示器let loadingView = UIView(frame: view.bounds)loadingView.backgroundColor = UIColor.black.withAlphaComponent(0.3)loadingView.tag = 9999let activityIndicator = UIActivityIndicatorView(style: .large)activityIndicator.center = loadingView.centeractivityIndicator.startAnimating()loadingView.addSubview(activityIndicator)view.addSubview(loadingView)}// 隐藏加载指示器func hideLoadingIndicator() {if let loadingView = view.viewWithTag(9999) {loadingView.removeFromSuperview()}}// 显示错误提示func showError(_ error: Error) {let alertController = UIAlertController(title: "错误", message: error.localizedDescription, preferredStyle: .alert)let okAction = UIAlertAction(title: "确定", style: .default, handler: nil)alertController.addAction(okAction)present(alertController, animated: true, completion: nil)}// 显示成功提示func showSuccess(message: String) {let alertController = UIAlertController(title: "成功", message: message, preferredStyle: .alert)let okAction = UIAlertAction(title: "确定", style: .default, handler: nil)alertController.addAction(okAction)present(alertController, animated: true, completion: nil)}// 平滑滚动到顶部func scrollToTop(animated: Bool = true) {if let tableView = view as? UITableView {if tableView.numberOfRows(inSection: 0) > 0 {tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: animated)}} else if let scrollView = view as? UIScrollView {scrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: animated)}}
}