iOS 键盘管理器:FJFKeyboardHelper

一. 前言

我们项目中很经常会碰到输入框键盘的遮挡问题,写多了我们会发现,不同视图和场景下处理逻辑大同小异,都是先注册监听键盘的弹起隐藏事件,然后在弹起隐藏的时候做处理,之后再移除键盘相关监听,因此你会发现,重复性代码挺多;虽然现在已经有了IQKeyboardManager这样优秀的第三方来管理,但有时候又会觉得IQKeyboardManager过于重量级,功能过于庞大,你可能不想引入这么庞大第三方,出于这个原因我自己写了一个键盘管理辅助类,来处理键盘输入框遮挡事件,核心代码一百多行处理只需要一句话就可以解决。

二.使用介绍

  • 使用方法

/**
 移除 键盘 管理器
 */
+ (void)removeKeyboardHelper;

/**
 更新 键盘 和 响应者 间距

 @param spacing 键盘 和 响应者 间距(默认0.5)
 */
+ (void)updateKeyboardTofirstResponderSpacing:(CGFloat)spacing;

/**
 处理 containerView 键盘 遮挡
 
 @param containerView 需要移动的视图
 */
+ (void)handleKeyboardWithContainerView:(UIView *)containerView;


/**
 处理 scrollView 键盘 遮挡(列表型)

 @param scrollView scrollView
 */
+ (void)handleKeyboardWithScrollView:(UIScrollView *)scrollView;


/**
 处理 键盘

 @param showBlock 显示 回调
 @param hideBlock 隐藏 回调
 */
+ (void)handleKeyboardWithShowBlock:(FJFKeyboardManagerBlock)showBlock hideBlock:(FJFKeyboardManagerBlock)hideBlock;

举个例子:

// 视图 调用
 [FJFKeyboardHelper handleKeyboardWithContainerView:self.view];
// 列表 调用
 [FJFKeyboardHelper handleKeyboardWithScrollView:self.tableView];
  • 集成方法:
静态:手动将FJFKeyboardHelper文件夹拖入到工程中。
动态:CocoaPods:pod 'FJFKeyboardHelper'
  • github 链接

Demo地址: https://github.com/fangjinfeng/FJFKeyboardHelper

  • 效果展示:
    FJFKeyboardHelper.gif

三. 原理分析

1. 原理简介

FJFKeyboardHelper2种可供选择的处理方法:
第一种:FJFKeyboardHelper来处理键盘遮挡
使用者提供键盘遮挡时需要移动的视图containerView,FJFKeyboardHelper通过注册键盘通知,在键盘弹起时,通过获取当前响应者相对于UIWindow的位置,来移动containerViewframe以此来解决键盘遮挡,在键盘隐藏时,将containerViewframe还原

第二种:使用者通过回调自己处理键盘遮挡
FJFKeyboardHelper注册键盘通知,在键盘弹起时,通过showBlock回调,在键盘隐藏时,通过hideBlock的回调,由使用者自己来处理键盘遮挡问题。

2. 代码分析:

FJFKeyboardHelper3个类方法:

第一种:处理普通视图输入框的键盘遮挡

/**
 处理 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将让当前UIViewControllerView强引用helper,防止helper出了作用域释放

UIViewController的类方法fjf_keyboardCurrentViewController用来获取当前界面UIViewController

UIView的实例方法fjf_setkeyboardHelper用来设置当前viewkeyboardHelper的关联。

  • 接着看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. 依据键盘的beginRectendRect判断出键盘最后一次回调,因为这里第三方键盘回调三次,这里只需要执行最后一次即可。

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这个键盘管理器的一个设计思路核心代码量也就一百来行,能处理大部分的键盘遮挡场景,而且只需要一句代码。

image.png

如果你觉得你觉得这思路或是代码有什么问题,欢迎留言大家讨论下!如果觉得不错,麻烦给个喜欢或star,谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    X先生_未知数的X阅读 15,937评论 3 118
  • 刚刚右眼跳的,我觉得早上的好预感已经不管用了,前面真的大坑来临。 从去年下半年开始到现在,我的事业越来越多坎坷。压...
    大丽哥阅读 194评论 0 0
  • 文/李大侠_ 闻风敲雨冷瑟极, 花颤止绽蕊自衰。 对窗饮酒愁为何, 爱人难见郁叹息。
    李大侠_阅读 217评论 2 0
  • 忧,来源于无法选择,面对却无从下手。 拖延症本就是人性之一,当面临真正的deadline的时候,我们会做点什么? ...
    百生居阅读 296评论 0 0