深入講解iOS鍵盤二:使用IQKeyboardManager解決鍵盤遮擋問(wèn)題

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

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


image

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


image

代碼應(yīng)該也很好理解:
-(void)viewDidLoad{
    [super viewDidLoad];
    //監(jiān)聽(tīng)鍵盤彈出通知
    [[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è)好的解決方案,那有沒(méi)有更好的解決方案呢蚀浆,答案是肯定的缀程,只是大家沒(méi)想到會(huì)有多么簡(jiǎn)單:
在工程的Podfile中添加如下這句話:

pod 'IQKeyboardManager'

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

image

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

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

默認(rèn)情況下IQKeyboardManager是開(kāi)啟的昭躺,如果我們需要針對(duì)某個(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中注冊(cè)方法:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [[IQKeyboardManager sharedManager] disableInViewControllerClass:[XXXViewController class]];
}
IQKeyboardManager工具條的顯示/隱藏
[IQKeyboardManager sharedManager].enableAutoToolbar = NO;
設(shè)置點(diǎn)擊背景收回鍵盤
[IQKeyboardManager sharedManager].shouldResignOnTouchOutside = YES;

下面我們來(lái)研究一下這個(gè)庫(kù)的源代碼伪嫁。有過(guò)一定開(kāi)發(fā)經(jīng)驗(yàn)的iOS程序員肯定是知道要從load方法開(kāi)始找起领炫,因?yàn)檫@個(gè)庫(kù)沒(méi)被引用即可生效,那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ù)中脚猾,對(duì)IQKeyboardManager這個(gè)單例實(shí)現(xiàn)了初始化葱峡,初始化重載了init方法。這里看到了我們熟悉的注冊(cè)通知的方法:

[strongSelf registerAllNotifications];

這個(gè)方法的實(shí)現(xiàn)中龙助,最主要的是監(jiān)聽(tīng)了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庫(kù)最重要的方法沽一,也是我們主要分析的方法盖溺。

-(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是正值铣缠,說(shuō)明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]];
        }
    }
    //以下代碼是為了對(duì)獲取到的ScrollView進(jìn)行判斷是否是當(dāng)前遮蓋鍵盤的ScrollView
    if (_lastScrollView){
    }{
    }
    //以下是對(duì)Top Layout guide做的特殊處理(如果大家不熟悉的話,沒(méi)關(guān)系蝗蛙,大概意思就是做個(gè)兼容)
    if (layoutGuidePosition == IQLayoutGuidePositionTop)
    {
    }
    else if (layoutGuidePosition == IQLayoutGuidePositionBottom){
    }
    else//這里是核心的用于處理鍵盤遮擋的代碼
    {
        //對(duì)textView可以通過(guò)設(shè)置ContentInset來(lái)達(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;
        }
        //  這里再對(duì)iPad做個(gè)特殊處理
        if ([rootController modalPresentationStyle] == UIModalPresentationFormSheet ||
            [rootController modalPresentationStyle] == UIModalPresentationPageSheet)
        {
        }
        else
        {
             //這里是“核心中的核心”蝇庭,設(shè)置View的Frame,以達(dá)到避免遮擋的目的捡硅。
             [self setRootViewFrame:rootViewRect];
        }
    }
}

好了哮内,以上是對(duì)IQKeyboardManager的使用的介紹以及源碼的分析,希望大家對(duì)這個(gè)非常方便的庫(kù)有個(gè)直觀的印象。當(dāng)然北发,這里筆者只是做了個(gè)簡(jiǎn)單的介紹纹因,如果大家有興趣可以深入的看一下它的源代碼,相信通過(guò)對(duì)IQKeyboardManager源代碼的閱讀琳拨,可以加深對(duì)iOS中的視圖層級(jí)的理解瞭恰。

引用

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市狱庇,隨后出現(xiàn)的幾起案子惊畏,更是在濱河造成了極大的恐慌,老刑警劉巖密任,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件颜启,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡批什,警方通過(guò)查閱死者的電腦和手機(jī)农曲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)驻债,“玉大人,你說(shuō)我怎么就攤上這事形葬『夏牛” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵笙以,是天一觀的道長(zhǎng)淌实。 經(jīng)常有香客問(wèn)我,道長(zhǎng)猖腕,這世上最難降的妖魔是什么拆祈? 我笑而不...
    開(kāi)封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮倘感,結(jié)果婚禮上放坏,老公的妹妹穿的比我還像新娘。我一直安慰自己老玛,他們只是感情好淤年,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著蜡豹,像睡著了一般麸粮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上镜廉,一...
    開(kāi)封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天弄诲,我揣著相機(jī)與錄音,去河邊找鬼娇唯。 笑死齐遵,一個(gè)胖子當(dāng)著我的面吹牛寂玲,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播洛搀,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼敢茁,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼尔许!你這毒婦竟也來(lái)了广料?” 一聲冷哼從身側(cè)響起汁雷,我...
    開(kāi)封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤丧靡,失蹤者是張志新(化名)和其女友劉穎伺绽,沒(méi)想到半個(gè)月后春瞬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體怔鳖,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡竿刁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年景图,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了较雕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡挚币,死狀恐怖亮蒋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情妆毕,我是刑警寧澤慎玖,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站笛粘,受9級(jí)特大地震影響趁怔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜薪前,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一润努、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧示括,春花似錦铺浇、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至繁涂,卻和暖如春拱她,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背扔罪。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工秉沼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓唬复,卻偏偏與公主長(zhǎng)得像矗积,于是被迫代替她去往敵國(guó)和親敞咧。 傳聞我的和親對(duì)象是個(gè)殘疾皇子棘捣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容