UIViewController-KeyboardAnimation使用6大常见坑清单:忘记取消订阅、Block循环引用如何避免
2026/8/27 15:20:29 网站建设 项目流程

UIViewController-KeyboardAnimation使用6大常见坑清单:忘记取消订阅、Block循环引用如何避免

【免费下载链接】UIViewController-KeyboardAnimationShowing/dismissing keyboard animation in UIViewController category.项目地址: https://gitcode.com/gh_mirrors/ui/UIViewController-KeyboardAnimation

UIViewController-KeyboardAnimation是一款轻量的 iOS 键盘动画开源组件,它通过 UIViewController 分类(Category)提供订阅式 API:键盘出现、消失或尺寸变化时,你的动画 Block 会自动以键盘原生动画的时长与曲线被执行,无需手写通知监听代码。本清单针对新手最常踩的 6 个坑——尤其是忘记取消订阅Block 循环引用——给出症状、原因和正确做法。

1 分钟了解:这个组件解决了什么 🎯

在 iOS 中处理键盘,通常需要监听UIKeyboardWillShowNotificationUIKeyboardWillHideNotification等通知,手动取出键盘高度和动画时长,再包一层UIView动画——每个页面都要复制一遍这套代码。

本组件把这些重复劳动封装成了 4 个订阅 API(定义在 UIViewController+KeyboardAnimation.h 中):

  • an_subscribeKeyboardWithAnimations:completion:—— 键盘出现/消失动画 + 完成回调
  • an_subscribeKeyboardWithBeforeAnimations:animations:completion:—— 额外提供"动画开始前"的执行时机
  • an_subscribeKeyboardFrameChangesWithAnimations:—— 响应键盘尺寸变化(如旋转屏幕时键盘高度改变)
  • an_unsubscribeKeyboard/an_unsubscribeKeyboardFrameChanges—— 取消订阅

官方示例(聊天输入框跟随键盘上移)可直接参考演示工程 ViewController.m,核心逻辑只有十几行。

快速上手:安装与最小订阅步骤

在 Podfile 中添加依赖后安装:

pod 'UIViewController+KeyboardAnimation', '~> 1.3'

也可以直接复制头文件与实现文件(UIViewController+KeyboardAnimation.hUIViewController+KeyboardAnimation.m)进工程,或使用 git submodule 方式引入仓库(仓库地址:https://gitcode.com/gh_mirrors/ui/UIViewController-KeyboardAnimation)。

最小订阅写法:

// viewWillAppear 中订阅 __weak typeof(self) weakSelf = self; [self an_subscribeKeyboardWithAnimations:^(CGRect keyboardRect, NSTimeInterval duration, BOOL isShowing) { __strong typeof(weakSelf) self = weakSelf; self.inputBottomSpace.constant = isShowing ? CGRectGetHeight(keyboardRect) : 0; [self.view layoutIfNeeded]; } completion:nil];

到这里就能跑起来,但以下 6 个坑不避开,轻则 UI 不跟随,重则内存泄漏。

6 大常见坑清单 ⚠️

坑 1:忘记调用 an_unsubscribeKeyboard —— 别页面上"幽灵"动画

这是使用率最高的 Bug。头文件中的官方警告原文大意是:如果当前视图消失时不取消订阅,已订阅的视图控制器会在其他页面上继续响应键盘事件

原因在实现里很直白:订阅时通过objc_setAssociatedObject把 Block 挂在视图控制器上(见 UIViewController+KeyboardAnimation.m),并注册了键盘通知观察者;只要视图控制器对象还活着(比如它还在导航栈或 Tab 里),通知照常派发,动画照常执行——哪怕页面早已不可见。

✅ 正确做法:在viewWillDisappear中取消订阅,与订阅成对出现:

- (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [self an_unsubscribeKeyboard]; }

⚠️ 额外注意:an_unsubscribeKeyboard只清理"出现/消失"通道的 Block 与观察者,不会清理键盘尺寸变化通道。如果你同时订阅了an_subscribeKeyboardFrameChangesWithAnimations:,必须再调用一次an_unsubscribeKeyboardFrameChanges,两条通道互不代替。

坑 2:Block 内强引用 self —— 键盘动画最常见的循环引用

这个坑的头文件里已经写明了(@warning These blocks will be holding inside UIViewController which calls it, so as with any block-style API avoid a retain cycle)。

循环链路是这样的:

  1. 视图控制器通过关联对象持有你传入的动画 Block(OBJC_ASSOCIATION_COPY_NONATOMIC会拷贝并强持有 Block 内容);
  2. Block 里又强引用self(视图控制器本身);
  3. 结果:控制器 → Block → 控制器,谁也不放手,视图控制器永远无法释放。

✅ 正确做法:Block 内使用弱引用,官方 README 示例采用 ReactiveCocoa 的@weakify/@strongify,不用该库时标准__weak写法完全等价:

__weak typeof(self) weakSelf = self; [self an_subscribeKeyboardWithAnimations:^(CGRect keyboardRect, NSTimeInterval duration, BOOL isShowing) { __strong typeof(weakSelf) self = weakSelf; if (!self) return; // 修改布局…… } completion:nil];

同样的规则适用于beforeAnimationsanimationscompletion三个 Block 参数——任何一个强捕获self都会形成循环引用。

坑 3:在 viewDidLoad 里订阅 —— 订阅时机不对

很多新手默认"初始化"就该放viewDidLoad,但组件文档对每个订阅 API 都标注了官方建议:viewWillAppear是订阅的最佳时机@tip viewWillAppear is the best place to subscribe to keyboard events)。

viewDidLoad订阅的问题:

  • 视图控制器可能已被创建但尚未显示(例如被 push 前的准备阶段),此时键盘状态可能已变化,而WillShow/WillHide通知只在状态切换瞬间发一次,错过就补不回来,导致页面出现时 UI 与键盘位置错乱;
  • 页面每次重新出现都会重走订阅逻辑,与坑 5 叠加会反复添加观察者。

✅ 正确做法:订阅放viewWillAppear,取消订阅放viewWillDisappear,让订阅的生命周期与页面可见性严格对齐(官方 Demo 正是这样做的,见 ViewController.m)。

坑 4:Auto Layout 改了约束却不调用 layoutIfNeeded —— 动画"跳变"不丝滑

动画 Block 是在UIView animate动画事务内部被调用的,这是本组件最大的卖点:你在 Block 里做的修改会自动享受键盘原生的时长和曲线。但有一个前提条件被新手频繁忽略——文档原话:"If using auto layout don't forget to call layoutIfNeeded"

如果你只修改了约束常量:

self.inputBottomSpace.constant = isShowing ? CGRectGetHeight(keyboardRect) : 0; // 漏了 [self.view layoutIfNeeded];

那么约束值虽然变了,但视图并没有在动画事务内真正布局,表现就是:输入框"瞬移"到目标位置,失去了与键盘同步滑动的效果。

✅ 正确做法:在动画 Block 的最后一行加上[self.view layoutIfNeeded],让约束变更进入当前动画事务。

坑 5:重复订阅 subscribe —— 按一次键盘,动画执行 N 次

an_subscribeKeyboardWithAnimations:completion:不是幂等的:每调用一次,都会执行一次addObserver注册新的通知观察者(见 UIViewController+KeyboardAnimation.m 中an_subscribeKeyboardWithAnimations:completion:的实现)。Block 本身会被新值覆盖,但观察者会叠加。

典型事故场景:把订阅写进了viewWillAppear之外的地方(如viewDidAppear里根据条件反复触发,或页面复用组件时重复初始化),订阅 3 次后,键盘每弹起一次,动画 Block 就被派发执行 3 次——布局被连续改写,动画卡顿甚至错乱。

✅ 正确做法:

  • 订阅只做一次,或确保"先an_unsubscribeKeyboard再重新订阅";
  • 分清两个通道:WillShow/WillHide(出现/消失)与WillChangeFrame(尺寸变化,如转屏后键盘高度变化)是两条独立的通知通道,按需选择,避免同一个页面同时订阅两套后又各自忘记清理(参考坑 1)。

坑 6:误读 isShowing 与 keyboardRect —— 键盘收起了,UI 却回不去

动画 Block 的第三个参数isShowing表示"这次是出现还是消失",而keyboardRect是键盘最终frame(屏幕坐标系)。两个高频误用:

  1. 不区分 isShowing 就写布局。键盘消失时 Block 同样会被调用(isShowingNO),如果只写"上移"逻辑、没有对应的"复位"分支,输入框就会永远悬在键盘收起后的位置。正确写法是对两个分支各写一套状态:
if (isShowing) { self.inputBottomSpace.constant = CGRectGetHeight(keyboardRect); } else { self.inputBottomSpace.constant = 0; }
  1. 直接拿 keyboardRect 当视图坐标用keyboardRect来自UIKeyboardFrameEndUserInfoKey,是窗口(屏幕)坐标系下的完整 frame,不要直接赋给视图的frame.origin;对"跟随键盘高度"类需求,取CGRectGetHeight(keyboardRect)即可,Demo 工程也是这么做的。

顺带一提:completion回调的参数finishedNO时表示动画在中途被打断(例如用户快速连点输入框),依赖动画完成的逻辑(如置回状态标志位)要判断这个值,官方复杂示例就是用它重置isKeaboardAnimation标记的。

6 大坑速查表 📋

#典型症状正确做法
1忘记取消订阅别的页面弹键盘,本页 UI 跟着动viewWillDisappearan_unsubscribeKeyboard;帧变化通道单独取消
2Block 强引用 self页面销毁后内存不回落Block 内__weak/@weakify弱引用
3viewDidLoad订阅页面出现时 UI 与键盘错位订阅放viewWillAppear,与取消订阅成对
4忘记layoutIfNeeded布局瞬移、失去动画同步感Auto Layout 场景在动画 Block 末尾补上
5重复订阅 / 混用双通道一次键盘事件触发多次动画订阅只做一次;两条通道分别管理
6误读 isShowing / keyboardRect键盘收起后输入框不回落分支处理复位;用CGRectGetHeight取高度

小结

UIViewController-KeyboardAnimation 的价值在于用极小的 API 面换掉了重复的键盘通知样板代码,而它把"订阅、取消订阅、Block 持有"这三件事的生命周期管理交还给了使用者。记住本文的 6 大坑,尤其是取消订阅成对调用Block 弱引用 self这两条核心纪律,基本可以覆盖新手阶段全部的高频事故。组件本体仅头文件和实现文件两个源文件(UIViewController+KeyboardAnimation.hUIViewController+KeyboardAnimation.m),配合 Demo 工程(Demo/KeyboardAnimationDemo/)通读一遍,你对它的所有行为都能建立确定性预期。

【免费下载链接】UIViewController-KeyboardAnimationShowing/dismissing keyboard animation in UIViewController category.项目地址: https://gitcode.com/gh_mirrors/ui/UIViewController-KeyboardAnimation

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

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

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

立即咨询