我们项目中很经常会碰到输入框
和键盘
的遮挡问题,写多了我们会发现,不同视图和场景下
的处理逻辑大同小异
,都是先注册监听
键盘的弹起
和隐藏
事件,然后在弹起
和隐藏
的时候做处理,之后再移除键盘
的相关监听
,因此你会发现,重复性代码
挺多;虽然现在已经有了IQKeyboardManager
这样优秀的第三方来管理,但有时候又会觉得IQKeyboardManager
过于重量级
,功能过于庞大
,你可能不想引入这么庞大
的第三方
,出于这个原因我自己写了一个键盘管理
的辅助类
,来处理键盘
和输入框
的遮挡
事件,核心代码
就一百多行
,处理
只需要一句话
就可以解决。
- 使用方法
/**
处理 containerView 键盘 遮挡
@param containerView 需要移动的视图
*/
+ (void)handleKeyboardWithContainerView:(UIView *)containerView;
/**
处理 scrollView 键盘 遮挡(列表型)
@param scrollView scrollView
*/
+ (void)handleKeyboardWithScrollView:(UIScrollView *)scrollView;
/**
处理 键盘
@param showBlock 显示 回调
@param hideBlock 隐藏 回调
*/
+ (void)handleKeyboardWithShowBlock:(MOAKeyboardManagerBlock)showBlock hideBlock:(MOAKeyboardManagerBlock)hideBlock;
- 集成方法:
静态:手动将FJFKeyboardHelper文件夹拖入到工程中。
动态:CocoaPods:pod 'FJFKeyboardHelper'
github 链接
- 效果展示:
FJFKeyboardHelper
有2种
可供选择的处理方法
:
第一种:FJFKeyboardHelper
来处理键盘遮挡
使用者
提供键盘遮挡
时需要移动的视图containerView
,FJFKeyboardHelper
通过注册键盘通知
,在键盘弹起
时,通过获取当前响应者
相对于UIWindow
的位置,来移动containerView
的frame
以此来解决键盘遮挡
,在键盘隐藏
时,将containerView
的frame还原
。
第二种:使用者
通过回调
自己处理键盘遮挡
FJFKeyboardHelper
的注册键盘通知
,在键盘弹起
时,通过showBlock
回调,在键盘隐藏
时,通过hideBlock
的回调,由使用者
自己来处理键盘遮挡
问题。
FJFKeyboardHelper
的3
个类方法:
第一种:处理普通视图输入框的键盘遮挡
/**
处理 containerView 键盘 遮挡
@param containerView 需要移动的视图
*/
+ (void)handleKeyboardWithContainerView:(UIView *)containerView;
-
方法调用
[FJFKeyboardHelper handleKeyboardWithContainerView:self.view];
-
FJFKeyboardHelper
的类方法+ (void)handleKeyboardWithContainerView:(UIView *)containerView
实现如下:
FJFKeyboardHelper *helper = [[FJFKeyboardHelper alloc] init];
[helper handleKeyboardWithContainerView:containerView];
[[UIViewController fjf_keyboardCurrentViewController].view fjf_setkeyboardHelper:helper];
这里的helper
是个局部变量
,为了延长局部变量
的生命周期
,通过函数fjf_setkeyboardHelper
将让当前UIViewController
的View
强引用helper
,防止helper
出了作用域
就释放
。
UIViewController
的类方法fjf_keyboardCurrentViewController
用来获取当前界面
的UIViewController
。
UIView
的实例方法fjf_setkeyboardHelper
用来设置当前view
和keyboardHelper
的关联。
- 接着看
FJFKeyboardHelper
的初始化方法init
,
- (instancetype)init {
if (self = [super init]) {
_oldContainerViewFrame = CGRectZero;
[self addKeyboardNotiObserver];
}
return self;
}
在init
方法这里将oldContainerViewFrame
初始化为CGRectZero
,同时注册键盘弹起
和隐藏
的通知事件
。
- (void)addKeyboardNotiObserver {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyBoardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyBoardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
- 接着看
helper
调用的实例方法handleKeyboardWithContainerView
- (void)handleKeyboardWithContainerView:(UIView *)containerView {
if ([containerView isKindOfClass:[UIView class]]) {
_containerView = containerView;
}
NSAssert([containerView isKindOfClass:[UIView class]], @"containerView 必现是 UIView类型");
}
这里只是进行简单的赋值
和断言操作
,判断当前containerView
是否为UIView
类型。
- 最后来看下
键盘弹起
的回调函数keyBoardWillShow
和键盘隐藏
的keyBoardWillHide
函数:
// 键盘 显示
- (void)keyBoardWillShow:(NSNotification *)noti {
if ([noti.name isEqualToString:UIKeyboardWillShowNotification]) {
NSDictionary *keyBordInfo = [noti userInfo];
NSValue *value = [keyBordInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
CGRect keyBoardRect = [value CGRectValue];
CGRect beginRect = [[keyBordInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue];
CGRect endRect = [[keyBordInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
if (CGRectEqualToRect(_oldContainerViewFrame, CGRectZero)) {
_oldContainerViewFrame = _containerView.frame;
}
// 第三方键盘回调三次问题,监听仅执行最后一次
if(beginRect.size.height > 0 && (beginRect.origin.y - endRect.origin.y > 0)){
// 有回调
if (self.keyboardShowBlock) {
self.keyboardShowBlock(noti.name, noti.userInfo, keyBoardRect);
}
// 无回调
else {
UIView *tmpView = [UIResponder fjf_keyboardCurrentFirstResponder];
if ([tmpView isKindOfClass:[UIView class]]) {
UIWindow * window = [[[UIApplication sharedApplication] delegate] window];
CGRect rect = [tmpView convertRect:tmpView.bounds toView:window];
CGFloat viewBottomHeight = [UIScreen mainScreen].bounds.size.height - CGRectGetMaxY(rect);
if (viewBottomHeight < 0) {
viewBottomHeight = 0;
}
CGFloat viewBottomOffset = keyBoardRect.size.height - viewBottomHeight;
NSString *durationValue = [keyBordInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey];
if (viewBottomOffset > 0 ) {
// 列表
if (_scrollView) {
CGFloat contentOffsetY = self.scrollView.contentOffset.y + viewBottomOffset;
[UIView animateWithDuration:durationValue.floatValue animations:^{
self.scrollView.contentOffset = CGPointMake(0, contentOffsetY);
}];
}
// 非列表
else if(_containerView){
CGFloat contentOffsetY = _oldContainerViewFrame.origin.y - viewBottomOffset;
[UIView animateWithDuration:durationValue.floatValue animations:^{
self.containerView.frame = CGRectMake(self.oldContainerViewFrame.origin.x, contentOffsetY, self.oldContainerViewFrame.size.width, self.oldContainerViewFrame.size.height);
}];
}
}
}
}
}
}
}
在键盘即将显示的回调函数里面:
a. 我们先判断_oldContainerViewFrame
是否为CGRectZero
,如果是将_containerView.frame
赋值给_oldContainerViewFrame
b. 依据键盘的beginRect
和endRect
判断出键盘
的最后一次
回调,因为这里第三方键盘
会回调三次
,这里只需要执行最后一次
即可。
c. 然后判断使用者
是否要自己处理键盘遮挡
,如果是,则直接通过block
回调给使用者
,如果不是,先去获取当前响应者
,依据响应者
算出相对UIWindow
的位置
,然后算出需要偏移
的偏移量
。
该函数 [UIResponder fjf_keyboardCurrentFirstResponder];
主要用来获取当前
的第一响应者
。
d. 接着判断当前需要移动的视图
是否为scrollView
类型,如果是scrollView
类型就通过设置contentOffset
来偏移,如果不是,就通过设置frame
来偏移。
// 键盘 隐藏
- (void)keyBoardWillHide:(NSNotification *)noti {
if ([noti.name isEqualToString:UIKeyboardWillHideNotification]) {
NSDictionary *keyBordInfo = [noti userInfo];
NSValue *value = [keyBordInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
CGRect keyBoardRect = [value CGRectValue];
// 有回调
if (self.keyboardHideBlock) {
self.keyboardHideBlock(noti.name, noti.userInfo, keyBoardRect);
}
// 无回调
else {
// 非列表
NSString *durationValue = [keyBordInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey];
if(_containerView){
[UIView animateWithDuration:durationValue.floatValue animations:^{
self.containerView.frame = self.oldContainerViewFrame;
self.oldContainerViewFrame = CGRectZero;
}];
}
}
}
}
在键盘即将隐藏的回调函数里面:
- 我们首先判断
使用者
是否设置回调来自己处理键盘隐藏
事件,如果有回调
,则直接通过block
回调给使用者
,如果没有
,再判断当前需要移动视图
是否为非scrollView类型
,如果是非scrollView类型
,就将之前存储的oldContainerViewFrame
赋值给self.containerView的frame
,并将self.oldContainerViewFrame
置为CGRectZero
。
这里之所以在键盘隐藏时候将self.oldContainerViewFrame
置为CGRectZero
,是为了保证获取到需要移动视图
的最新的frame
,以防止中间过程
中可能需要移动的视图
的frame会被修改
,而FJFKeyboardHelper
的实例里面却不是最新的
。
第二种:处理列表输入框的键盘遮挡
/**
处理 scrollView 键盘 遮挡(列表型)
@param scrollView scrollView
*/
+ (void)handleKeyboardWithScrollView:(UIScrollView *)scrollView;
这个方法跟第一个处理普通视图输入框的键盘遮挡
方法差不多,唯一的区
别就是键盘即将显示
的时候,列表
是通过设置contentOffset
来进行偏移
,普通视图
是通过设置frame
来进行偏移
。
第三种:使用者自己处理键盘回调
/**
处理 键盘
@param showBlock 显示 回调
@param hideBlock 隐藏 回调
*/
+ (void)handleKeyboardWithShowBlock:(MOAKeyboardManagerBlock)showBlock hideBlock:(MOAKeyboardManagerBlock)hideBlock;
这个方法的处理是相对比较简单的,就是在键盘即将显示
和键盘即将隐藏
的回调函数
里面,通过block
回调给使用者
自己处理。
综上所述就是FJFKeyboardHelper
这个键盘管理器
的一个设计思路
,核心代码量
也就一百来行
,能处理大部分的键盘遮挡场景
,而且只需要一句代码。
如果你觉得你觉得这思路或是代码有什么问题,欢迎留言大家讨论下!