☰
FDFullscreenPopGesture 源码级解析:用 AOP 为 UINavigationController 开启全屏滑动返回
2026/10/15 2:18:05 网站建设 项目流程
  • 移动开发
  • UI组件

【免费下载链接】FDFullscreenPopGesture

A UINavigationController's category to enable fullscreen pop gesture with iOS7+ system style.

项目地址:https://gitcode.com/gh_mirrors/fd/FDFullscreenPopGesture
点击查看免费下载

FDFullscreenPopGesture 是一个面向UINavigationController的轻量级分类(Category),以 AOP(面向切面编程)方式为 iOS 7+ 系统风格的导航栈注入"全屏"滑动返回能力——开发者只需将两个源文件加入工程,所有导航控制器的边缘滑动手势即可扩展为全屏手势。本文以仓库 README.md 为主线,结合 UINavigationController+FDFullscreenPopGesture.m 源码与 Demo 工程,讲解其集成方式、全部公开 API、底层 swizzle 原理、导航栏联动以及 ScrollView 冲突处理,读完即可在真实项目中直接落地这套全屏返回方案。

一、背景:iOS 7 系统默认滑动返回的局限

自 iOS 7 起,UINavigationController自带interactivePopGestureRecognizer(屏幕边缘滑动返回手势),但它的有效触发区域仅限于屏幕左边缘,且由系统内部私有实现管理,开发者难以直接扩展它的触发范围、拦截条件或联动自定义行为。许多 App 希望实现"从屏幕任意位置右滑即可返回"的体验,这就是 FDFullscreenPopGesture 要解决的问题。

该扩展的思路来自 @J_雨 的一篇博客文章:与其重新实现一套转场动画,不如复用系统已有的交互式 Pop 转场内部处理逻辑,仅仅替换/扩充手势识别器本身。仓库 README.md 明确说明它是一套 "iOS7+ system style" 的方案,即手势触发的转场动画、控制器的层级关系完全沿用系统实现,开发者得到的是"原汁原味"的返回动画,只是触发区域从屏幕边缘变成了整个屏幕。

上图(Snapshots/snapshot0.gif)展示了在页面中部(远离左边缘的位置)发起右滑即可完整返回上一级页面的效果。

二、快速开始:两个文件 + 零配置

FDFullscreenPopGesture 的接入成本极低。它采用 AOP 设计:只要把下面两个文件加入 target,即通过+load自动完成运行时方法交换(Method Swizzling),所有UINavigationController都会自动获得全屏返回手势,不需要任何额外设置:

  • UINavigationController+FDFullscreenPopGesture.h:公开 API 声明
  • UINavigationController+FDFullscreenPopGesture.m:实现文件(接入时即编译进工程)

从源码结构看,真正依赖的只有上述分类文件;仓库中的 FDFullscreenPopGesture.podspec 里s.source_files = "FDFullscreenPopGesture/*.{h,m}"、s.requires_arc = true也印证了这一点。分类中的两个+load方法分别在UIViewController与UINavigationController加载时执行 swizzle:

  • UIViewController:交换viewWillAppear:与viewWillDisappear:,用于导航栏外观联动(见第四节);
  • UINavigationController:交换pushViewController:animated:,用于在首次 push 时注入全屏手势并接管系统的边缘手势(见第三节源码分析)。

接入后默认行为是所有导航栈都支持全屏返回,而以下两个开关可以让你在"导航控制器"或"单个视图控制器"粒度上精确控制:

三、核心 API 一览

分类暴露了 5 个公开属性,全部集中在 UINavigationController+FDFullscreenPopGesture.h 中,分别作用于UINavigationController与UIViewController两个层面:

属性所属分类类型默认值作用
fd_fullscreenPopGestureRecognizerUINavigationControllerUIPanGestureRecognizer *(readonly)首次访问时惰性创建实际处理全屏返回的手势识别器,可对其enabled做整体开关
fd_viewControllerBasedNavigationBarAppearanceEnabledUINavigationControllerBOOLYES是否启用"基于视图控制器"的导航栏显隐联动
fd_interactivePopDisabledUIViewControllerBOOLNO当前视图控制器是否禁用交互式返回手势
fd_prefersNavigationBarHiddenUIViewControllerBOOLNO当前视图控制器是否偏好隐藏导航栏(仅在前一开关开启时生效)
fd_interactivePopMaxAllowedInitialDistanceToLeftEdgeUIViewControllerCGFloat0(不限制)允许发起返回手势的起点距屏幕左边缘的最大初始距离

注:fd_interactivePopMaxAllowedInitialDistanceToLeftEdge是 README 未展开、但在头文件与 Demo 中都实际使用的补充能力,默认0表示不限制起始位置;若设置为200,则手势起点距左边缘超过 200pt 时不会被触发。其 setter 在源码 UINavigationController+FDFullscreenPopGesture.m 中通过MAX(0, distance)做了非负钳制。

所有属性均通过objc_getAssociatedObject/objc_setAssociatedObject以关联对象方式存取,因此不需要子类化任何类,对既有 ViewController 零侵入。

四、手势的禁用与启用

4.1 禁用某个导航控制器的全屏返回

navigationController.fd_fullscreenPopGestureRecognizer.enabled = NO;

fd_fullscreenPopGestureRecognizer是一个真正的UIPanGestureRecognizer实例,直接关闭它的enabled即可让该导航控制器下的所有页面失去全屏返回能力。需要重新开启时设回YES。

4.2 禁用某个视图控制器的全屏返回

viewController.fd_interactivePopDisabled = YES;

这是 README 推荐的最常用方式:当某个页面自身处理了平移手势、或业务上不允许返回时,在该页面设置此属性,手势代理会直接忽略此页面上的右滑动作(判定逻辑见下节源码)。需要与 README 保持一致:该能力要求iOS 7.0 及以上,与 FDFullscreenPopGesture.podspec 中s.platform = :ios, "7.0"一致。

五、源码原理:如何让"全屏手势"驱动"系统转场"

5.1 注入全屏手势并"借道"系统内部 handler

在UINavigationController的+load中,分类对pushViewController:animated:做了 swizzle,交换后的fd_pushViewController:animated:在首次 push 时执行一次性的手势接管(UINavigationController+FDFullscreenPopGesture.m):

  1. 把自定义的全屏UIPanGestureRecognizer(maximumNumberOfTouches = 1,即单指识别)添加到系统边缘手势所在的同一个 view上,从而覆盖整个屏幕;
  2. 通过 KVC 从系统手势的targets数组中取出内部 target,将手势事件转发到系统私有方法handleNavigationTransition:——返回转场的核心动画逻辑仍然由系统执行;
  3. 将自定义手势的delegate设置为内部的_FDFullscreenPopGestureRecognizerDelegate,用于按需放行/拦截手势;
  4. 最后关闭系统自带的interactivePopGestureRecognizer.enabled,避免双手势冲突。

这样做的巧妙之处在于:全屏手势与系统边缘手势"共享同一套转场 handler",视觉动画、控制器栈的 pop 行为完全由系统保证,扩展点只存在于手势识别层面。

5.2 手势放行的五重判定

_FDFullscreenPopGestureRecognizerDelegate在gestureRecognizerShouldBegin:(UINavigationController+FDFullscreenPopGesture.m)中按顺序做如下判定,任一条件不满足则拒绝本次手势:

  1. 栈中页面数不足:viewControllers.count <= 1(根控制器无可返回页面)→ 不允许;
  2. 当前页面禁用手势:topViewController.fd_interactivePopDisabled为 YES → 不允许;
  3. 起点距左边缘过远:fd_interactivePopMaxAllowedInitialDistanceToLeftEdge > 0且手势起点 x 超过该值 → 不允许;
  4. 导航栈正在转场:_isTransitioning为 YES → 不允许(避免转场期间手势干扰);
  5. 手势方向不符:判断translation.x的方向,LTR(左到右布局)要求向右滑(translation.x > 0),并通过对userInterfaceLayoutDirection的判断乘上正负 multiplier,以兼容 RTL 语言环境(如阿拉伯语)下镜像布局的返回方向。

这套判定覆盖了"栈为空、页面禁用、距离限制、转场中、方向错误"等全部边界场景,保证全屏手势不会破坏导航栈的稳定性。

六、基于 ViewController 的导航栏显隐联动

在全屏手势 push/pop 时,系统默认的导航栏行为往往会让"部分页面有栏、部分页面无栏"的应用出现导航栏状态错乱。FDFullscreenPopGesture 通过 swizzleviewWillAppear:/viewWillDisappear:(UINavigationController+FDFullscreenPopGesture.m),在转场发生的正确时机按目标页面自身偏好设置导航栏,正确处理了以下三种过渡:

  • 有栏页面 → 无栏页面
  • 无栏页面 → 有栏页面
  • 无栏页面 → 无栏页面

上图(Snapshots/snapshot1.gif)演示了在不同导航栏显隐状态的页面间 push/pop 时,导航栏能随目标页面正确出现或隐藏,不再需要全局手动调用-setNavigationBarHidden:animated:。

该优化默认开启(fd_viewControllerBasedNavigationBarAppearanceEnabled默认YES,getter 在未设置时返回 YES)。开启后,你不再需要手动调用UINavigationController的-setNavigationBarHidden:animated:,而是让每个 ViewController 声明自己的偏好:

- (void)viewDidLoad { [super viewDidLoad]; // 声明本页面不显示导航栏 self.fd_prefersNavigationBarHidden = YES; }

fd_prefersNavigationBarHidden的默认值为NO,即默认显示导航栏。在转场时,分类会将"正在出现的页面"与"正在消失的页面"都注入同一个fd_willAppearInjectBlock,由 block 调用setNavigationBarHidden:animated:完成联动;同时在viewWillDisappear:的延迟块中兜底恢复导航栏,保证无论何种路径(push、setViewControllers:等)进入的页面,导航栏状态都能收敛正确。

值得一提的是,Demo 工程直接在 Main.storyboard 的 User Defined Runtime Attributes 里配置了这些属性(如fd_prefersNavigationBarHidden = YES、fd_interactivePopMaxAllowedInitialDistanceToLeftEdge = 200),说明这些属性同样支持在 storyboard/XIB 中通过 KVC 方式静态配置,无需写代码即可实现"某页无栏 + 限制返回触发距离"的组合效果。

七、与 ScrollView 的共存:手势同识别的正确姿势

全屏返回手势是全屏UIPanGestureRecognizer,当页面内存在UIScrollView(或UITableView/UICollectionView等子类)且其contentOffset.x <= 0(横向滚动已到最左)时,需要允许两个手势同时识别,否则系统手势优先级会导致无法右滑返回。

正确做法是自定义 ScrollView 子类,重写gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:,并检测对手手势的 delegate 是否为 FDFullscreenPopGesture 的私有代理类_FDFullscreenPopGestureRecognizerDelegate:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { if (self.contentOffset.x <= 0) { if ([otherGestureRecognizer.delegate isKindOfClass:NSClassFromString(@"_FDFullscreenPopGestureRecognizerDelegate")]) { return YES; } } return NO; }

要点:

  • self.contentOffset.x <= 0:仅当 ScrollView 横向滚动已处于最左位置时才放行,避免与页面内的横向滑动(如横向分页浏览)冲突;
  • NSClassFromString(@"_FDFullscreenPopGestureRecognizerDelegate"):用类名精确识别全屏返回手势,不要用[gestureRecognizer.view isKindOfClass:]之类的宽泛判断,避免误放行页面内其他普通手势;
  • 当contentOffset.x > 0(正在横向滚动内容)时返回 NO,让 ScrollView 优先处理自己的滑动。

仓库 Demo 中的 FDDemoScrollView.m 就是这份代码的直接落地实现,storyboard 中"2 . Push a view controller with scrollView"一页(Main.storyboard)使用pagingEnabled的横向分页 ScrollView 验证了此方案。

八、安装方式

8.1 CocoaPods(推荐)

在Podfile中写入:

pod 'FDFullscreenPopGesture', '1.1'

然后执行pod install即可。Podspec(FDFullscreenPopGesture.podspec)约定的关键信息:

字段值
version1.1
platform:ios, "7.0"
source_filesFDFullscreenPopGesture/*.{h,m}
requires_arctrue

即该库只包含头文件 + 实现文件两个源码文件,要求 ARC,最低支持 iOS 7.0。

8.2 手动集成

直接将 UINavigationController+FDFullscreenPopGesture.h 与 UINavigationController+FDFullscreenPopGesture.m 拖入 Xcode 工程(确保开启 ARC)即可,分类的+load会自动生效,无需调用任何初始化方法。

8.3 运行 Demo 验证

仓库自带 FDFullscreenPopGestureDemo 工程,入口是一个三行列表:

  • "0 . Push a view controller with bar":普通有栏页面(Main.storyboard 中对应 forkingdog 场景);
  • "1 . Push a view controller without bar":无栏页面,且限制返回手势初始距离为 200pt;
  • "2 . Push a view controller with scrollView":使用FDDemoScrollView的横向分页页面,验证手势共存。

用 Xcode 打开 FDFullscreenPopGestureDemo.xcodeproj 直接运行,即可在真机/模拟器上逐一验证全屏返回、导航栏联动与 ScrollView 共存三个核心能力。

九、版本演进

README 中的 Release Notes 记录了该库的演进脉络:

  • 1.0— 全屏返回手势(核心能力,AOP 自动注入);
  • 1.1— 新增基于 ViewController 的导航栏外观与转场联动(即fd_viewControllerBasedNavigationBarAppearanceEnabled与fd_prefersNavigationBarHidden),以及 ScrollView 手势共存支持。

当前仓库代码即为 1.1 形态,涵盖了上述全部能力。

十、许可证

仓库采用MIT协议(LICENSE),允许自由使用、修改、分发与商用,仅需保留版权声明。

总结:什么时候选择这套方案

FDFullscreenPopGesture 的核心价值在于"以最小侵入换取系统级转场一致性":通过分类 + swizzle + 关联对象,既不需要子类化UINavigationController,也不需要重写转场动画,就能让所有页面获得全屏返回能力,同时保留系统的返回动画与导航栏联动。适用场景包括:

  • 想要全局开启全屏返回、但又不想引入自绘转场框架的导航型 App;
  • 需要按页面(fd_interactivePopDisabled)或按手势起点区域(fd_interactivePopMaxAllowedInitialDistanceToLeftEdge)精细化控制返回行为的场景;
  • 页面内存在横向 ScrollView,需要精细处理手势共存的应用。

如果在你的工程中遇到手势识别顺序问题,优先检查两点:是否为 ScrollView 子类重写了同识别回调,以及判定用的 delegate 类名是否准确——这正是本节源码中最容易踩坑的地方。

  • 移动开发
  • UI组件

【免费下载链接】FDFullscreenPopGesture

A UINavigationController's category to enable fullscreen pop gesture with iOS7+ system style.

项目地址:https://gitcode.com/gh_mirrors/fd/FDFullscreenPopGesture
点击查看免费下载
上一篇:Trino中的内存分页技术:Spill to Disk实现原理
下一篇:Apache Doris数据导出实战:Export工具完整使用指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询