深入讲解iOS键盘二:使用IQKeyboardManager解决键盘遮挡问题

本系列博客是本人的开发笔记。欢迎一起讨论

大家在日常开发中应该知道,表单提交页面是比较难处理的,一是因为UI上需要处理UITextField/UITextView的样式以及键盘的显示隐藏;二是因为在处理接口的时候一般是POST方式需要做各种校验;三是如果提交失败,还要做各种后续诸如弹框等的处理工作。下面笔者要给大家展示的就是一个我们日常开发中可能遇到的信息填写页面,这次我们要实现的效果类似于iMessage中的用户信息编辑页面:


image

可以看到,当我们点击位于屏幕下方的TextField时,弹出键盘,但随之TableView也往上滑动了一下,这样就避免了键盘遮挡输入框的问题。根据上一篇文章的知识点,实现的方式相信大家应该很容易就能想到,无非是监听到键盘弹出时更改TableView的ContentOffset值。这里,笔者做了个简单的Demo,实现的效果如下:


image

代码应该也很好理解:
-(void)viewDidLoad{
    [super viewDidLoad];
    //监听键盘弹出通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyBoardWillShowWithNotification:) name:UIKeyboardWillChangeFrameNotification object:nil];
}
//在Cell中加入UITextField,并设置Delegate为Self
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    static NSString *cellIdentifier = @"cellIdentifier";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
        UITextField *field = [[UITextField alloc] initWithFrame:CGRectMake(15, 10, SCREEN_WIDTH - 30, 24)];
        field.delegate = self;
        field.placeholder = [NSString stringWithFormat:@"You can input Something At Index %li",(long)indexPath.row];
        [cell.contentView addSubview:field];
    }
    
    return cell;
}
//在即将编辑之前计算出TextField的位置,并转化成TableView中的位置
- (void)textFieldDidBeginEditing:(UITextField *)textField {
    self.activedTextFieldRect = [textField convertRect:textField.frame toView:self.tableview];
}
//当键盘弹出时,动态改变TableView的ContentOffset的值
- (void)keyBoardWillShowWithNotification:(NSNotification *)notification {
    //get FrameEnd
    CGRect rect = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
    //get AnimationDuration
    double duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
    //keyboard height > textView height, need scroll
    if ((self.activedTextFieldRect.origin.y + self.activedTextFieldRect.size.height) >  ([UIScreen mainScreen].bounds.size.height - rect.size.height))
    {
        [UIView animateWithDuration:duration animations:^{
            self.tableview.contentOffset = CGPointMake(0, 64 + self.activedTextFieldRect.origin.y + self.activedTextFieldRect.size.height - ([UIScreen mainScreen].bounds.size.height - rect.size.height));
        }];
    }
}

以上代码可以点击这里从github获取到

可以看到,这么处理的一个繁琐的地方是,必须要在TableViewCell中获取到TextField,并将TextField的Delegate设置为当前的控制器。这显然不是一个好的解决方案,那有没有更好的解决方案呢,答案是肯定的,只是大家没想到会有多么简单:
在工程的Podfile中添加如下这句话:

pod 'IQKeyboardManager'

即可。是的,引入IQKeyboardManager库即可。他会自动帮我们解决如上问题,下面我们看一下删除我们刚刚的代码,并加入库IQKeyboardManager后的效果:

image

可以看到我们的键盘上面多了一个小的工具栏,可以选择上下箭头来切换需要填写的TextField,我们的PlaceHolder也显示在了这个小工具栏上。鉴于有些读者可能对这个库的使用不是很熟悉,这里对其使用做个简单的介绍:

IQKeyboardManager的开启/关闭
 [IQKeyboardManager sharedManager].enable = YES;

默认情况下IQKeyboardManager是开启的,如果我们需要针对某个控制器关闭,可以做这样处理:

-(void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [IQKeyboardManager sharedManager].enable = NO;
}

-(void)viewWillDisappear:(BOOL)animated {
    [IQKeyboardManager sharedManager].enable = YES;
    [super viewWillDisappear:animated];
}

或者在 AppDelegate中注册方法:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [[IQKeyboardManager sharedManager] disableInViewControllerClass:[XXXViewController class]];
}
IQKeyboardManager工具条的显示/隐藏
[IQKeyboardManager sharedManager].enableAutoToolbar = NO;
设置点击背景收回键盘
[IQKeyboardManager sharedManager].shouldResignOnTouchOutside = YES;

下面我们来研究一下这个库的源代码。有过一定开发经验的iOS程序员肯定是知道要从load方法开始找起,因为这个库没被引用即可生效,那99%的可能性是使用了这个iOS中的“黑魔法”。
load方法位于类IQKeyboardManager中,实现如下:

+(void)load
{
    //Enabling IQKeyboardManager. Loading asynchronous on main thread
    [self performSelectorOnMainThread:@selector(sharedManager) withObject:nil waitUntilDone:NO];
}

可以看到,load函数中,对IQKeyboardManager这个单例实现了初始化,初始化重载了init方法。这里看到了我们熟悉的注册通知的方法:

[strongSelf registerAllNotifications];

这个方法的实现中,最主要的是监听了UITextFieldTextDidBeginEditingNotification通知

[self registerTextFieldViewClass:[UITextField class]
     didBeginEditingNotificationName:UITextFieldTextDidBeginEditingNotification
       didEndEditingNotificationName:UITextFieldTextDidEndEditingNotification];

因此,当UITextField获取焦点后,在方法

-(void)textFieldViewDidBeginEditing:(NSNotification*)notification

中进行了添加键盘工具栏,调整ScrollView等可能被键盘遮挡的View的相应属性。具体的调整方法位于

-(void)adjustFrame

中。这也是IQKeyboardManager库最重要的方法,也是我们主要分析的方法。

-(void)adjustFrame
{
    UIWindow *keyWindow = [self keyWindow];
    //获取当前控制器
    UIViewController *rootController = [_textFieldView topMostController];
    if (rootController == nil)  rootController = [keyWindow topMostWindowController];
    //获取TextField(或TextView)的Frame
    CGRect textFieldViewRect = [[_textFieldView superview] convertRect:_textFieldView.frame toView:keyWindow];
    CGRect rootViewRect = [[rootController view] frame];

    //省略部分代码....
    //获取移动的高度,如果move是正值,说明textField被隐藏,否则则是被显示了
    CGFloat move = 0;
    if (layoutGuidePosition == IQLayoutGuidePositionBottom)
    {
        move = CGRectGetMaxY(textFieldViewRect)-(CGRectGetHeight(keyWindow.frame)-kbSize.height);
    }
    else
    {
        move = MIN(CGRectGetMinY(textFieldViewRect)-(topLayoutGuide+5), CGRectGetMaxY(textFieldViewRect)-(CGRectGetHeight(keyWindow.frame)-kbSize.height));
    }

    //省略部分代码...
    //找到视图中可能存在的ScrollView
    UIScrollView *superView = (UIScrollView*)[_textFieldView superviewOfClassType:[UIScrollView class]];
    while (superView)
    {
        if (superView.isScrollEnabled && superView.shouldIgnoreScrollingAdjustment == NO)
        {
            superScrollView = superView;
            break;
        }
        else
        {
            //  Getting it's superScrollView.   //  (Enhancement ID: #21, #24)
            superView = (UIScrollView*)[superView superviewOfClassType:[UIScrollView class]];
        }
    }
    //以下代码是为了对获取到的ScrollView进行判断是否是当前遮盖键盘的ScrollView
    if (_lastScrollView){
    }{
    }
    //以下是对Top Layout guide做的特殊处理(如果大家不熟悉的话,没关系,大概意思就是做个兼容)
    if (layoutGuidePosition == IQLayoutGuidePositionTop)
    {
    }
    else if (layoutGuidePosition == IQLayoutGuidePositionBottom){
    }
    else//这里是核心的用于处理键盘遮挡的代码
    {
        //对textView可以通过设置ContentInset来达到效果
       if ([_textFieldView isKindOfClass:[UITextView class]])
        {
            UIEdgeInsets newContentInset = textView.contentInset;
            newContentInset.bottom = strongSelf.textFieldView.frame.size.height-textViewHeight;
            textView.contentInset = newContentInset;
            textView.scrollIndicatorInsets = newContentInset;
            strongSelf.isTextViewContentInsetChanged = YES;
        }
        //  这里再对iPad做个特殊处理
        if ([rootController modalPresentationStyle] == UIModalPresentationFormSheet ||
            [rootController modalPresentationStyle] == UIModalPresentationPageSheet)
        {
        }
        else
        {
             //这里是“核心中的核心”,设置View的Frame,以达到避免遮挡的目的。
             [self setRootViewFrame:rootViewRect];
        }
    }
}

好了,以上是对IQKeyboardManager的使用的介绍以及源码的分析,希望大家对这个非常方便的库有个直观的印象。当然,这里笔者只是做了个简单的介绍,如果大家有兴趣可以深入的看一下它的源代码,相信通过对IQKeyboardManager源代码的阅读,可以加深对iOS中的视图层级的理解。

引用

『零行代码』解决键盘遮挡问题(iOS)
IQKeyboardManager源码分析

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

推荐阅读更多精彩内容