46

iOS 键盘管理器:FJFKeyboardHelper

 5 years ago
source link: http://www.cocoachina.com/ios/20190320/26612.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

一. 前言

我们项目中很经常会碰到输入框和键盘的遮挡问题,写多了我们会发现,不同视图和场景下的处理逻辑大同小异,都是先注册监听键盘的弹起和隐藏事件,然后在弹起和隐藏的时候做处理,之后再移除键盘的相关监听,因此你会发现,重复性代码挺多;虽然现在已经有了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

  • 效果展示:

jAVJni2.gif

三. 原理分析

1. 原理简介

FJFKeyboardHelper有2种可供选择的处理方法:

第一种:FJFKeyboardHelper来处理键盘遮挡

使用者提供键盘遮挡时需要移动的视图containerView,FJFKeyboardHelper通过注册键盘通知,在键盘弹起时,通过获取当前响应者相对于UIWindow的位置,来移动containerView的frame以此来解决键盘遮挡,在键盘隐藏时,将containerView的frame还原。

第二种:使用者通过回调自己处理键盘遮挡

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

2. 代码分析:

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

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

作者:林大鹏天地

链接:https://www.jianshu.com/p/34b50c7a111f


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK