深入講解iOS鍵盤二:使用IQKeyboardManager解決鍵盤遮擋問題

本系列博客是本人的開發(fā)筆記。歡迎一起討論

大家在日常開發(fā)中應(yīng)該知道,表單提交頁面是比較難處理的,一是因?yàn)閁I上需要處理UITextField/UITextView的樣式以及鍵盤的顯示隱藏;二是因?yàn)樵谔幚斫涌诘臅r(shí)候一般是POST方式需要做各種校驗(yàn);三是如果提交失敗,還要做各種后續(xù)諸如彈框等的處理工作。下面筆者要給大家展示的就是一個(gè)我們?nèi)粘i_發(fā)中可能遇到的信息填寫頁面,這次我們要實(shí)現(xiàn)的效果類似于iMessage中的用戶信息編輯頁面:


image

可以看到,當(dāng)我們點(diǎn)擊位于屏幕下方的TextField時(shí),彈出鍵盤,但隨之TableView也往上滑動(dòng)了一下,這樣就避免了鍵盤遮擋輸入框的問題。根據(jù)上一篇文章的知識點(diǎn),實(shí)現(xiàn)的方式相信大家應(yīng)該很容易就能想到,無非是監(jiān)聽到鍵盤彈出時(shí)更改TableView的ContentOffset值。這里,筆者做了個(gè)簡單的Demo,實(shí)現(xiàn)的效果如下:


image

代碼應(yīng)該也很好理解:
-(void)viewDidLoad{
    [super viewDidLoad];
    //監(jiān)聽鍵盤彈出通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyBoardWillShowWithNotification:) name:UIKeyboardWillChangeFrameNotification object:nil];
}
//在Cell中加入U(xiǎn)ITextField,并設(shè)置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;
}
//在即將編輯之前計(jì)算出TextField的位置,并轉(zhuǎn)化成TableView中的位置
- (void)textFieldDidBeginEditing:(UITextField *)textField {
    self.activedTextFieldRect = [textField convertRect:textField.frame toView:self.tableview];
}
//當(dāng)鍵盤彈出時(shí),動(dòng)態(tài)改變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));
        }];
    }
}

以上代碼可以點(diǎn)擊這里從github獲取到

可以看到,這么處理的一個(gè)繁瑣的地方是,必須要在TableViewCell中獲取到TextField,并將TextField的Delegate設(shè)置為當(dāng)前的控制器。這顯然不是一個(gè)好的解決方案,那有沒有更好的解決方案呢,答案是肯定的,只是大家沒想到會(huì)有多么簡單:
在工程的Podfile中添加如下這句話:

pod 'IQKeyboardManager'

即可。是的,引入IQKeyboardManager庫即可。他會(huì)自動(dòng)幫我們解決如上問題,下面我們看一下刪除我們剛剛的代碼,并加入庫IQKeyboardManager后的效果:

image

可以看到我們的鍵盤上面多了一個(gè)小的工具欄,可以選擇上下箭頭來切換需要填寫的TextField,我們的PlaceHolder也顯示在了這個(gè)小工具欄上。鑒于有些讀者可能對這個(gè)庫的使用不是很熟悉,這里對其使用做個(gè)簡單的介紹:

IQKeyboardManager的開啟/關(guān)閉
 [IQKeyboardManager sharedManager].enable = YES;

默認(rèn)情況下IQKeyboardManager是開啟的,如果我們需要針對某個(gè)控制器關(guān)閉,可以做這樣處理:

-(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;
設(shè)置點(diǎn)擊背景收回鍵盤
[IQKeyboardManager sharedManager].shouldResignOnTouchOutside = YES;

下面我們來研究一下這個(gè)庫的源代碼。有過一定開發(fā)經(jīng)驗(yàn)的iOS程序員肯定是知道要從load方法開始找起,因?yàn)檫@個(gè)庫沒被引用即可生效,那99%的可能性是使用了這個(gè)iOS中的“黑魔法”。
load方法位于類IQKeyboardManager中,實(shí)現(xiàn)如下:

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

可以看到,load函數(shù)中,對IQKeyboardManager這個(gè)單例實(shí)現(xiàn)了初始化,初始化重載了init方法。這里看到了我們熟悉的注冊通知的方法:

[strongSelf registerAllNotifications];

這個(gè)方法的實(shí)現(xiàn)中,最主要的是監(jiān)聽了UITextFieldTextDidBeginEditingNotification通知

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

因此,當(dāng)UITextField獲取焦點(diǎn)后,在方法

-(void)textFieldViewDidBeginEditing:(NSNotification*)notification

中進(jìn)行了添加鍵盤工具欄,調(diào)整ScrollView等可能被鍵盤遮擋的View的相應(yīng)屬性。具體的調(diào)整方法位于

-(void)adjustFrame

中。這也是IQKeyboardManager庫最重要的方法,也是我們主要分析的方法。

-(void)adjustFrame
{
    UIWindow *keyWindow = [self keyWindow];
    //獲取當(dāng)前控制器
    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];

    //省略部分代碼....
    //獲取移動(dòng)的高度,如果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進(jìn)行判斷是否是當(dāng)前遮蓋鍵盤的ScrollView
    if (_lastScrollView){
    }{
    }
    //以下是對Top Layout guide做的特殊處理(如果大家不熟悉的話,沒關(guān)系,大概意思就是做個(gè)兼容)
    if (layoutGuidePosition == IQLayoutGuidePositionTop)
    {
    }
    else if (layoutGuidePosition == IQLayoutGuidePositionBottom){
    }
    else//這里是核心的用于處理鍵盤遮擋的代碼
    {
        //對textView可以通過設(shè)置ContentInset來達(dá)到效果
       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做個(gè)特殊處理
        if ([rootController modalPresentationStyle] == UIModalPresentationFormSheet ||
            [rootController modalPresentationStyle] == UIModalPresentationPageSheet)
        {
        }
        else
        {
             //這里是“核心中的核心”,設(shè)置View的Frame,以達(dá)到避免遮擋的目的。
             [self setRootViewFrame:rootViewRect];
        }
    }
}

好了,以上是對IQKeyboardManager的使用的介紹以及源碼的分析,希望大家對這個(gè)非常方便的庫有個(gè)直觀的印象。當(dāng)然,這里筆者只是做了個(gè)簡單的介紹,如果大家有興趣可以深入的看一下它的源代碼,相信通過對IQKeyboardManager源代碼的閱讀,可以加深對iOS中的視圖層級的理解。

引用

『零行代碼』解決鍵盤遮擋問題(iOS)
IQKeyboardManager源碼分析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容