- 移动开发
- UI组件
【免费下载链接】FDFullscreenPopGesture
A UINavigationController's category to enable fullscreen pop gesture with iOS7+ system style.
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_fullscreenPopGestureRecognizer | UINavigationController | UIPanGestureRecognizer *(readonly) | 首次访问时惰性创建 | 实际处理全屏返回的手势识别器,可对其enabled做整体开关 |
fd_viewControllerBasedNavigationBarAppearanceEnabled | UINavigationController | BOOL | YES | 是否启用"基于视图控制器"的导航栏显隐联动 |
fd_interactivePopDisabled | UIViewController | BOOL | NO | 当前视图控制器是否禁用交互式返回手势 |
fd_prefersNavigationBarHidden | UIViewController | BOOL | NO | 当前视图控制器是否偏好隐藏导航栏(仅在前一开关开启时生效) |
fd_interactivePopMaxAllowedInitialDistanceToLeftEdge | UIViewController | CGFloat | 0(不限制) | 允许发起返回手势的起点距屏幕左边缘的最大初始距离 |
注:
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):
- 把自定义的全屏
UIPanGestureRecognizer(maximumNumberOfTouches = 1,即单指识别)添加到系统边缘手势所在的同一个 view上,从而覆盖整个屏幕; - 通过 KVC 从系统手势的
targets数组中取出内部 target,将手势事件转发到系统私有方法handleNavigationTransition:——返回转场的核心动画逻辑仍然由系统执行; - 将自定义手势的
delegate设置为内部的_FDFullscreenPopGestureRecognizerDelegate,用于按需放行/拦截手势; - 最后关闭系统自带的
interactivePopGestureRecognizer.enabled,避免双手势冲突。
这样做的巧妙之处在于:全屏手势与系统边缘手势"共享同一套转场 handler",视觉动画、控制器栈的 pop 行为完全由系统保证,扩展点只存在于手势识别层面。
5.2 手势放行的五重判定
_FDFullscreenPopGestureRecognizerDelegate在gestureRecognizerShouldBegin:(UINavigationController+FDFullscreenPopGesture.m)中按顺序做如下判定,任一条件不满足则拒绝本次手势:
- 栈中页面数不足:
viewControllers.count <= 1(根控制器无可返回页面)→ 不允许; - 当前页面禁用手势:
topViewController.fd_interactivePopDisabled为 YES → 不允许; - 起点距左边缘过远:
fd_interactivePopMaxAllowedInitialDistanceToLeftEdge > 0且手势起点 x 超过该值 → 不允许; - 导航栈正在转场:
_isTransitioning为 YES → 不允许(避免转场期间手势干扰); - 手势方向不符:判断
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)约定的关键信息:
| 字段 | 值 |
|---|---|
version | 1.1 |
platform | :ios, "7.0" |
source_files | FDFullscreenPopGesture/*.{h,m} |
requires_arc | true |
即该库只包含头文件 + 实现文件两个源码文件,要求 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.
相关推荐
FDFullscreenPopGesture 全屏侧滑返回实战指南:AOP 原理、API 用法与 ZFPlayer 集成案例
FDFullscreenPopGesture 全屏侧滑返回实战指南:AOP 原理、API 用法与 ZFPlayer 集成案例 导读 FDFullscreenPo
音视频移动开发FDFullscreenPopGesture:终极iOS全屏返回手势解决方案
FDFullscreenPopGesture:终极iOS全屏返回手势解决方案 想要为你的iOS应用添加流畅的全屏返回手势体验吗?FDFullscreenPopG
移动开发UI组件iOS全屏返回手势终极指南:如何自定义FDFullscreenPopGesture行为
iOS全屏返回手势终极指南:如何自定义FDFullscreenPopGesture行为 FDFullscreenPopGesture是一个强大的iOS导航控制器
移动开发UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考