- 音视频
- 移动开发
【免费下载链接】ZFPlayer
Support customization of any player SDK and control layer(支持定制任何播放器SDK和控制层)
导读
FDFullscreenPopGesture 是 iOS 开发中解决「全屏侧滑返回」这一经典痛点的开源方案,它以 AOP(面向切面)方式为UINavigationController注入全屏滑动返回手势,让用户可以从屏幕任意位置右滑返回,而不是只能从屏幕左边缘触发系统原生手势。本文以 Example/Pods/FDFullscreenPopGesture/README.md 为骨架,结合仓库内 头文件 与 实现文件 的源码,逐层拆解其 AOP 接入方式、四个核心 API、底层 swizzle 与手势转发机制,并给出它在 ZFPlayer 示例工程(抖音沉浸式播放页)中的真实落地用法。读完本文,你将能独立完成全屏返回手势的接入、按页关闭、以及「基于 ViewController 的导航栏显隐」配置,并理解其实现原理。
一、它是什么:iOS 全屏侧滑返回的 AOP 方案
从 iOS 7 开始,系统为UINavigationController提供了边缘侧滑返回手势(interactivePopGestureRecognizer),但该手势只在屏幕左边缘的一小块区域生效。FDFullscreenPopGesture 通过一个UINavigationController的分类(Category)扩展了这一行为:如头文件注释所述,它把「边缘滑动」扩展为「全屏滑动」——
extends UINavigationController's swipe-to-pop behavior in iOS 7+ by supporting fullscreen pan gesture. Instead of screen edge, you can now swipe from any place on the screen and the onboard interactive pop transition works seamlessly.
(见 UINavigationController+FDFullscreenPopGesture.h)
其核心设计思想是AOP:不要求你继承UINavigationController、不要求逐个配置,只要把实现文件加入 target,分类的+load方法就会自动对全局的UINavigationController行为进行「补丁式」改造。README 的原文描述是:
AOP, just add 2 files andno needfor any setups, all navigation controllers will be able to use fullscreen pop gesture automatically.
Require at least iOS7.0.
也就是说:接入成本只有「添加文件 + import 头文件」,所有导航控制器自动获得全屏返回能力,最低支持 iOS 7.0。该扩展思路最初来自 @J_雨 的一篇文章(README 中引用了原始出处)。本仓库的 README 位于 Example/Pods/FDFullscreenPopGesture/README.md,对应源码与 LICENSE 均在同目录下。
二、零配置接入:两个文件全局生效
README 明确说明只需要添加2 个文件(.h+.m),无需任何额外配置:
- UINavigationController+FDFullscreenPopGesture.h:声明分类与全部对外属性;
- UINavigationController+FDFullscreenPopGesture.m:全部实现,包括
+loadswizzle、手势注入与导航栏显隐管理。
头文件中的一段注释再次印证了「自动生效」的接入方式:
Adding the implementation file of this category to your target will automatically patch UINavigationController with this feature.
(见 UINavigationController+FDFullscreenPopGesture.h)
在 ZFPlayer 示例工程中,该库通过 CocoaPods 集成(详见 Example/Podfile),构建产物为静态库libFDFullscreenPopGesture.a(见 Example/Pods/Pods.xcodeproj/project.pbxproj 中的productReference配置)。使用方只需要在目标文件中#import "UINavigationController+FDFullscreenPopGesture.h"即可调用全部 API,示例工程里的 ZFDouYinViewController.m 与 ZFDouyinCollectionViewController.m 都是这样接入的。
三、核心 API 详解
整个库对外暴露 4 个属性,分属UINavigationController与UIViewController两个分类。下表汇总了它们的名称、所属分类、默认值与用途(默认值以头文件注释与实现源码为准):
| 属性 | 所属分类 | 默认值 | 作用 |
|---|---|---|---|
fd_fullscreenPopGestureRecognizer(readonly) | UINavigationController | 首次访问时惰性创建 | 真正处理全屏返回的UIPanGestureRecognizer,可通过enabled整体开关 |
fd_interactivePopDisabled | UIViewController | NO | 单独关闭某个视图控制器的侧滑返回手势 |
fd_viewControllerBasedNavigationBarAppearanceEnabled | UINavigationController | YES | 是否开启「基于 ViewController 的导航栏显隐」优化(README 称该优化默认开启) |
fd_prefersNavigationBarHidden | UIViewController | NO(即默认显示导航栏) | 声明单个页面是否偏好隐藏导航栏 |
3.1 关闭某个导航控制器的全屏返回
README 给出的用法是直接操作手势识别器本身:
navigationController.fd_fullscreenPopGestureRecognizer.enabled = NO;由于fd_fullscreenPopGestureRecognizer是UIPanGestureRecognizer的实例,设置enabled = NO后整个导航栈都会失去滑动返回响应。从源码看,该手势在 getter 中惰性创建,且限定为单指手势(maximumNumberOfTouches = 1),见 UINavigationController+FDFullscreenPopGesture.m。
3.2 关闭某个视图控制器的返回手势
更精细的控制粒度是「按页面关闭」,README 示例:
viewController.fd_interactivePopDisabled = YES;该属性挂在UIViewController分类上,通过关联对象存储(objc_setAssociatedObject),见 实现文件。它会被手势代理在gestureRecognizerShouldBegin:中读取,用于决定是否放行手势(详见第四节)。典型场景是:页面自身需要处理 pan 手势(例如播放器的手势控制、轮播图翻页)时,为避免冲突可以关闭系统返回手势。
3.3 导航栏显隐优化的总开关
fd_viewControllerBasedNavigationBarAppearanceEnabled用于控制「基于 ViewController 的导航栏显隐」功能是否生效。该属性挂在UINavigationController分类上,默认值为YES——getter 在未设置任何值时会主动写回YES并返回,见 UINavigationController+FDFullscreenPopGesture.m,头文件注释也写明Default to YES(头文件 L39-L40)。如果不需要该优化,可以在应用启动时统一关闭:
navigationController.fd_viewControllerBasedNavigationBarAppearanceEnabled = NO;3.4 单页导航栏显隐偏好
fd_prefersNavigationBarHidden挂在UIViewController分类上,用于声明「这个页面希望隐藏/显示导航栏」,配合上面的总开关使用。头文件注释明确了其语义与默认值(UINavigationController+FDFullscreenPopGesture.h):
Indicate this view controller prefers its navigation bar hidden or not, checked when view controller based navigation bar's appearance is enabled. Default to NO, bars are more likely to show.
即:设为YES表示隐藏导航栏;未设置时默认NO,导航栏显示。实现上它同样是关联对象存取(实现文件 L217-L225)。这里需要说明一点:README 中写道「And this property isYESby default.」,结合头文件注释与 getter 实现来看,源码层面默认开启的是「基于 ViewController 的导航栏显隐」这一优化本身(即fd_viewControllerBasedNavigationBarAppearanceEnabled默认YES),而fd_prefersNavigationBarHidden未设置时返回NO(显示导航栏)。使用时应以源码默认值为准。
四、底层原理:从 +load 到手势转发(源码级解读)
FDFullscreenPopGesture 的精髓在于用两个+loadswizzle + 一个系统私有 API 的复用,实现了「零配置全局生效」。下面按调用链拆解 UINavigationController+FDFullscreenPopGesture.m 的实现。
4.1 两个 +load 方法完成方法交换
第一个 swizzle:UIViewController分类在+load中交换viewWillAppear:(L73-L78),这样每个页面出现时都会额外执行注入的 block(fd_willAppearInjectBlock),为导航栏显隐优化提供时机钩子(L80-L88)。
第二个 swizzle:UINavigationController分类在+load中交换pushViewController:animated:(L104-L110),拦截每次 push 动作完成手势注入与导航栏配置。
4.2 fd_pushViewController: 中的手势注入
每次 push 新页面时,fd_pushViewController:animated:(L112-L135)会完成三件事:
- 挂载自定义手势:如果导航控制器的 view 上还没有注入过全屏手势,就把
fd_fullscreenPopGestureRecognizer添加到系统interactivePopGestureRecognizer.view(即导航控制器的根视图)上(L114-L117); - 复用系统私有处理逻辑:通过 KVC 读取系统手势的
targets数组,取出内部 target 与handleNavigationTransition:action,把全屏手势的事件转发给这个系统私有方法(L120-L124)。这正是「全屏手势 + 系统原生转场动画」无缝衔接的关键——动画表现与系统边缘滑动完全一致; - 禁用系统边缘手势:
self.interactivePopGestureRecognizer.enabled = NO;(L127),避免边缘手势与全屏手势重复触发。
4.3 手势代理的判定逻辑
_FDFullscreenPopGestureRecognizerDelegate实现了gestureRecognizerShouldBegin:(L34-L59),决定手势何时真正生效,共 4 个条件:
- 导航栈中页面数 ≤ 1:根页面没有可返回的页面,直接忽略(L37-L39);
- 当前页面关闭了返回:
topViewController.fd_interactivePopDisabled为 YES 时忽略(L42-L45); - 正在转场中:通过 KVC 读取
_isTransitioning判断,转场期间忽略,避免手势与 push/pop 动画冲突(L48-L50); - 滑动方向:只有
translation.x > 0(向右滑)才响应,向左滑交给页面自身处理(L53-L56)。
4.4 关联对象与惰性初始化
所有属性和手势对象都通过objc_getAssociatedObject/objc_setAssociatedObject存储(如 L90-L98),因此不需要任何 ivar、不需要子类化,这也是它能以 Category 实现全功能的前提。fd_popGestureRecognizerDelegate同样是惰性创建并持有(L161-L172)。
五、基于 ViewController 的导航栏显隐:三种状态组合与实现机制
5.1 解决什么问题
系统默认的导航栏显隐是「全局开关」式的:一旦调用setNavigationBarHidden:,整个导航栈的导航栏都会变。而在「A 页有导航栏、B 页无导航栏」这种常见设计下,用全屏手势滑动返回时,导航栏的过渡动画会出现「跳变」。FDFullscreenPopGesture 的优化让导航栏显隐跟随单个 ViewController 的状态,并正确驱动过渡动画。README 列出了它能正确处理的三类组合:
- with bar -> without bar(有导航栏页面返回无导航栏页面)
- without bar -> with bar(无导航栏页面返回有导航栏页面)
- without bar -> without bar(两个都无导航栏的页面之间返回)
5.2 使用方式:不再手动调用 setNavigationBarHidden:
README 给出的推荐做法是:不再调用UINavigationController的-setNavigationBarHidden:animated:,而是让每个页面声明自己的偏好:
- (void)viewDidLoad { [super viewDidLoad]; self.fd_prefersNavigationBarHidden = NO; }(将值改为YES即隐藏该页导航栏。)该优化默认开启,开发者无需额外配置。
5.3 实现机制:willAppear 注入 block
实现上,fd_pushViewController:会调用fd_setupViewControllerBasedNavigationBarAppearanceIfNeeded:(L137-L159),为「即将出现的页面」和「即将消失的页面」注入同一个 block,block 内部在viewWillAppear:时机执行:
[strongSelf setNavigationBarHidden:viewController.fd_prefersNavigationBarHidden animated:animated];见 L144-L149。两个细节值得注意:
- 之所以同时给「消失页面」也注入 block,是因为并非所有页面都是通过
pushViewController:入栈的,也可能是通过-setViewControllers:批量设置,注释对此有明确说明(L152-L157); - 只有总开关
fd_viewControllerBasedNavigationBarAppearanceEnabled为 YES 时才执行该逻辑(L139-L141)。
六、安装与版本
6.1 CocoaPods 安装
README 给出的安装命令(指定 1.1 版本):
pod 'FDFullscreenPopGesture', '1.1'本仓库的示例工程在 Example/Podfile 中通过pod 'FDFullscreenPopGesture'(未锁版本)引入,实际锁定版本为1.1,见 Example/Pods/Manifest.lock。示例工程最低部署版本为 iOS 8.0(见 Example/Podfile),满足该库「至少 iOS 7.0」的要求。
6.2 Release Notes
README 的版本记录:
- 1.1- View controller based navigation bar appearance and transition.(即第五节介绍的导航栏显隐优化)
- 1.0- Fullscreen pop gesture.(全屏侧滑返回手势)
6.3 License
MIT License,许可证文件位于 Example/Pods/FDFullscreenPopGesture/LICENSE,源码文件头部也带有 MIT 版权声明。
七、在 ZFPlayer 示例工程中的实战:抖音沉浸式播放页
本仓库的 ZFPlayer 示例工程把 FDFullscreenPopGesture 用在了最典型的场景——抖音风格全屏沉浸式播放页:页面隐藏系统导航栏,由播放器占据整屏,用户仍可通过全屏右滑返回上一级。
两个抖音样式页面都在viewDidLoad中设置:
self.fd_prefersNavigationBarHidden = YES;- ZFDouYinViewController.m:UITableView 竖向滚动的抖音样式播放列表;
- ZFDouyinCollectionViewController.m:UICollectionView 横向/竖向滚动的抖音样式页面。
结合第五节可以看到完整的协同链路:页面声明fd_prefersNavigationBarHidden = YES(隐藏导航栏)→ push 时注入 willAppear block → 页面出现时自动执行setNavigationBarHidden:animated:(带动画过渡)→ 返回时同样按「目标页面的偏好」恢复导航栏。全程无需手动管理导航栏状态,返回手势在任何位置都能触发,与抖音式交互一致。
7.1 与播放器手势的配合建议
ZFPlayer 的播放器在控制层内置了音量、亮度、进度调节等 pan/pinch 手势(相关实现见 ZFPlayerGestureControl.m)。当页面同时使用全屏返回手势与播放器手势时,建议注意两点:
- 播放器处于全屏/横屏状态时,通常不希望右滑返回,可配合
fd_interactivePopDisabled在进入全屏时临时关闭返回手势,退出全屏时恢复; - 若页面自身需要接管 pan 手势(例如轮播、横向翻页),同样用
fd_interactivePopDisabled = YES关闭该页返回手势,避免手势冲突。
八、常见问题与最佳实践小结
- 为什么「添加文件即全局生效」?因为
+load在分类加载时执行 swizzle,且手势注入发生在每次pushViewController:时,不需要任何手动初始化;若想整体禁用,直接用fd_fullscreenPopGestureRecognizer.enabled = NO。 - 边缘滑动与全屏滑动能否共存?不能也不需要共存:注入全屏手势后系统边缘手势会被显式禁用(L127),转场动画由同一个系统私有方法
handleNavigationTransition:驱动,体验与系统原生一致。 - 个别页面不想被右滑返回?使用
fd_interactivePopDisabled = YES,按页面粒度关闭,且该判断位于手势代理的最前端(L42-L45),开销极小。 - 导航栏显隐异常?确认总开关
fd_viewControllerBasedNavigationBarAppearanceEnabled未被误设为 NO,且每个页面在viewDidLoad中尽早声明fd_prefersNavigationBarHidden。 - 转场期间滑动无效?这是代理主动忽略
_isTransitioning期间手势的预期行为(L48-L50),用于保证转场动画不被中途打断,属于正常保护机制。
FDFullscreenPopGesture 以极小的代码体积(两个文件)解决了 iOS 全屏返回与导航栏过渡两大高频痛点,其 AOP 设计思路(+loadswizzle + 关联对象 + 复用系统私有 API)本身也是值得 iOS 开发者研读的经典范本。结合 ZFPlayer 示例工程中的抖音样式页面,你可以快速把它复用到自己的播放器、Feed 流或全屏沉浸式页面中。
- 音视频
- 移动开发
【免费下载链接】ZFPlayer
Support customization of any player SDK and control layer(支持定制任何播放器SDK和控制层)
相关推荐
CANN ops-transformer AllGatherAdd 算子详解:AllGather 通信与 Add 计算融合的两段式 aclnn 接口开发实战
CANN ops transformer AllGatherAdd 算子详解:AllGather 通信与 Add 计算融合的两段式 aclnn 接口开发实战 本
移动开发UI组件winget 命令从入门到精通:一次搞定 Windows 软件安装、批量管理与自动升级
winget 命令从入门到精通:一次搞定 Windows 软件安装、批量管理与自动升级 如果你是一个经常重装系统、或者需要给多台电脑配环境的开发者,一定经历过这
包管理器CLIiOS全屏返回手势终极指南:FDFullscreenPopGesture复杂项目实战应用
iOS全屏返回手势终极指南:FDFullscreenPopGesture复杂项目实战应用 想要为你的iOS应用添加流畅的全屏返回手势体验吗?FDFullscre
移动开发UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考