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

關(guān)注倉庫莽红,及時(shí)獲得更新:iOS-Source-Code-Analyze

Follow: Draveness · Github

Icon
Icon

這篇文章會(huì)對(duì) IQKeyboardManager 自動(dòng)解決鍵盤遮擋問題的方法進(jìn)行分析乱灵。

最近在項(xiàng)目中使用了 IQKeyboardManager 來解決 UITextField 被鍵盤遮擋的問題秃流,這個(gè)框架的使用方法可以說精簡到了極致房交,只需要將 IQKeyboardManager 加入 Podfile,然后 pod install 就可以了。

pod 'IQKeyboardManager'

這篇文章的題目《零行代碼解決鍵盤遮擋問題》來自于開源框架的介紹:

Codeless drop-in universal library allows to prevent issues of keyboard sliding up and cover UITextField/UITextView. Neither need to write any code nor any setup required and much more.

因?yàn)樵陧?xiàng)目中使用了 IQKeyboardManager队橙,所以,我想通過閱讀其源代碼來了解這個(gè)黑箱是如何工作的萨惑。

雖然這個(gè)框架的實(shí)現(xiàn)的方法是比較簡單的捐康,不過它的實(shí)現(xiàn)代碼不是很容易閱讀,框架因?yàn)榘撕芏嗯c UI 有關(guān)的實(shí)現(xiàn)細(xì)節(jié)庸蔼,所以代碼比較復(fù)雜解总。

架構(gòu)分析

說是架構(gòu)分析,其實(shí)只是對(duì) IQKeyboardManager 中包含的類以及文件有一個(gè)粗略地了解姐仅,研究一下這個(gè)項(xiàng)目的層級(jí)是什么樣的花枫。

IQKeyboardManager-Hierarchy

整個(gè)項(xiàng)目中最核心的部分就是 IQKeyboardManager 這個(gè)類,它負(fù)責(zé)管理鍵盤出現(xiàn)或者隱藏時(shí)視圖移動(dòng)的距離萍嬉,是整個(gè)框架中最核心的部分乌昔。

在這個(gè)框架中還有一些用于支持 IQKeyboardManager 的分類,以及顯示在鍵盤上面的 IQToolBar:

IQToolBa

使用紅色標(biāo)記的部分就是 IQToolBar壤追,左側(cè)的按鈕可以在不同的 UITextField 之間切換磕道,中間的文字是 UITextField.placeholderText,右邊的 Done 應(yīng)該就不需要解釋了行冰。

這篇文章會(huì)主要分析 IQKeyboardManager 中解決的問題溺蕉,會(huì)用小篇幅介紹包含占位符(Placeholder) IQTextView 的實(shí)現(xiàn)伶丐。

IQTextView 的實(shí)現(xiàn)

在具體研究如何解決鍵盤遮擋問題之前,我們先分析一下框架中最簡單的一部分 IQTextView 是如何為 UITextView 添加占位符的疯特。

@interface IQTextView : UITextView

@end

IQTextView 繼承自 UITextView哗魂,它只是在 UITextView 上添加上了一個(gè) placeHolderLabel

在初始化時(shí)漓雅,我們會(huì)為 UITextViewTextDidChangeNotification 注冊通知:

- (void)initialize   {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshPlaceholder) name:UITextViewTextDidChangeNotification object:self];
}

在每次 UITextView 中的 text 更改時(shí)录别,就會(huì)調(diào)用 refreshPlaceholder 方法更新 placeHolderLabelalpha 值來隱藏或者顯示 label:

-(void)refreshPlaceholder {
    if ([[self text] length]) {
        [placeHolderLabel setAlpha:0];
    } else {
        [placeHolderLabel setAlpha:1];
    }
    
    [self setNeedsLayout];
    [self layoutIfNeeded];
}

IQKeyboardManager

下面就會(huì)進(jìn)入這篇文章的正題:IQKeyboardManager

如果你對(duì) iOS 開發(fā)比較熟悉邻吞,可能會(huì)發(fā)現(xiàn)每當(dāng)一個(gè)類的名字中包含了 manager组题,那么這個(gè)類可能可能遵循單例模式,IQKeyboardManager 也不例外抱冷。

IQKeyboardManager 的初始化

當(dāng) IQKeyboardManager 初始化的時(shí)候崔列,它做了這么幾件事情:

  1. 監(jiān)聽有關(guān)鍵盤的通知

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidHide:) name:UIKeyboardDidHideNotification object:nil];
    
  2. 注冊與 UITextField 以及 UITextView 有關(guān)的通知

    [self registerTextFieldViewClass:[UITextField class]
     didBeginEditingNotificationName:UITextFieldTextDidBeginEditingNotification
       didEndEditingNotificationName:UITextFieldTextDidEndEditingNotification];
    
    [self registerTextFieldViewClass:[UITextView class]
     didBeginEditingNotificationName:UITextViewTextDidBeginEditingNotification
       didEndEditingNotificationName:UITextViewTextDidEndEditingNotification];
    
    • 調(diào)用的方法將通知綁定到了 textFieldViewDidBeginEditing:textFieldViewDidEndEditing: 方法上

      - (void)registerTextFieldViewClass:(nonnull Class)aClass
        didBeginEditingNotificationName:(nonnull NSString *)didBeginEditingNotificationName
          didEndEditingNotificationName:(nonnull NSString *)didEndEditingNotificationName {
          [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textFieldViewDidBeginEditing:) name:didBeginEditingNotificationName object:nil];
          [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textFieldViewDidEndEditing:) name:didEndEditingNotificationName object:nil];
      }
      
  3. 初始化一個(gè) UITapGestureRecognizer,在點(diǎn)擊 UITextField 對(duì)應(yīng)的 UIWindow 的時(shí)候旺遮,收起鍵盤

    strongSelf.tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapRecognized:)];
    
    - (void)tapRecognized:(UITapGestureRecognizer*)gesture {
        if (gesture.state == UIGestureRecognizerStateEnded)
            [self resignFirstResponder];
    }
    
  4. 初始化一些默認(rèn)屬性赵讯,例如鍵盤距離、覆寫鍵盤的樣式等

    strongSelf.animationDuration = 0.25;
    strongSelf.animationCurve = UIViewAnimationCurveEaseInOut;
    [self setKeyboardDistanceFromTextField:10.0];
    [self setShouldPlayInputClicks:YES];
    [self setShouldResignOnTouchOutside:NO];
    [self setOverrideKeyboardAppearance:NO];
    [self setKeyboardAppearance:UIKeyboardAppearanceDefault];
    [self setEnableAutoToolbar:YES];
    [self setPreventShowingBottomBlankSpace:YES];
    [self setShouldShowTextFieldPlaceholder:YES];
    [self setToolbarManageBehaviour:IQAutoToolbarBySubviews];
    [self setLayoutIfNeededOnUpdate:NO];
    
  5. 設(shè)置不需要解決鍵盤遮擋問題的類

    strongSelf.disabledDistanceHandlingClasses = [[NSMutableSet alloc] initWithObjects:[UITableViewController class], nil];
    strongSelf.enabledDistanceHandlingClasses = [[NSMutableSet alloc] init];
    
    strongSelf.disabledToolbarClasses = [[NSMutableSet alloc] init];
    strongSelf.enabledToolbarClasses = [[NSMutableSet alloc] init];
    
    strongSelf.toolbarPreviousNextAllowedClasses = [[NSMutableSet alloc] initWithObjects:[UITableView class],[UICollectionView class],[IQPreviousNextView class], nil];
    
    strongSelf.disabledTouchResignedClasses = [[NSMutableSet alloc] init];
    strongSelf.enabledTouchResignedClasses = [[NSMutableSet alloc] init];
    

整個(gè)初始化方法大約有幾十行的代碼耿眉,在這里就不再展示整個(gè)方法的全部代碼了边翼。

基于通知的解決方案

在這里,我們以 UITextField 為例跷敬,分析方法的調(diào)用流程讯私。

在初始化方法中,我們注冊了很多的通知西傀,包括鍵盤的出現(xiàn)和隱藏,UITextField 開始編輯與結(jié)束編輯桶癣。

UIKeyboardWillShowNotification
UIKeyboardWillHideNotification
UIKeyboardDidHideNotification
UITextFieldTextDidBeginEditingNotification
UITextFieldTextDidEndEditingNotification

在這些通知響應(yīng)時(shí)拥褂,會(huì)執(zhí)行以下的方法:

Notification Selector
UIKeyboardWillShowNotification @selector(keyboardWillShow:)
UIKeyboardWillHideNotification @selector(keyboardWillHide:)
UIKeyboardDidHideNotification @selector(keyboardDidHide:)
UITextFieldTextDidBeginEditingNotification @selector(textFieldViewDidBeginEditing:)
UITextFieldTextDidEndEditingNotification @selector(textFieldViewDidEndEditing:)

整個(gè)解決方案其實(shí)都是基于 iOS 中的通知系統(tǒng)的;在事件發(fā)生時(shí)牙寞,調(diào)用對(duì)應(yīng)的方法做出響應(yīng)饺鹃。

開啟 Debug 模式

在閱讀源代碼的過程中,我發(fā)現(xiàn) IQKeyboardManager 提供了 enableDebugging 這一屬性间雀,可以通過開啟它悔详,來追蹤方法的調(diào)用,我們可以在 Demo 加入下面這行代碼:

[IQKeyboardManager sharedManager].enableDebugging = YES;

鍵盤的出現(xiàn)

然后運(yùn)行工程惹挟,在 Demo 中點(diǎn)擊一個(gè) UITextField

easiest-integration-demo

上面的操作會(huì)打印出如下所示的 Log:

IQKeyboardManager: ****** textFieldViewDidBeginEditing: started ******
IQKeyboardManager: adding UIToolbars if required
IQKeyboardManager: Saving <UINavigationController 0x7f905b01b000> beginning Frame: {{0, 0}, {320, 568}}
IQKeyboardManager: ****** adjustFrame started ******
IQKeyboardManager: Need to move: -451.00
IQKeyboardManager: ****** adjustFrame ended ******
IQKeyboardManager: ****** textFieldViewDidBeginEditing: ended ******
IQKeyboardManager: ****** keyboardWillShow: started ******
IQKeyboardManager: ****** adjustFrame started ******
IQKeyboardManager: Need to move: -154.00
IQKeyboardManager: ****** adjustFrame ended ******
IQKeyboardManager: ****** keyboardWillShow: ended ******

我們可以通過分析 - textFieldViewDidBeginEditing: 以及 - keyboardWillShow: 方法來了解這個(gè)項(xiàng)目的原理茄螃。

textFieldViewDidBeginEditing:

當(dāng) UITextField 被點(diǎn)擊時(shí),方法 - textFieldViewDidBeginEditing: 被調(diào)用连锯,但是注意這里的方法并不是代理方法归苍,它只是一個(gè)跟代理方法同名的方法用狱,根據(jù) Log,它做了三件事情:

  • UITextField 添加 IQToolBar
  • 在調(diào)整 frame 前拼弃,保存當(dāng)前 frame夏伊,以備之后鍵盤隱藏后的恢復(fù)
  • 調(diào)用 - adjustFrame 方法,將視圖移動(dòng)到合適的位置

添加 ToolBar

添加 ToolBar 是通過方法 - addToolbarIfRequired 實(shí)現(xiàn)的吻氧,在 - textFieldViewDidBeginEditing: 先通過 - privateIsEnableAutoToolbar 判斷 ToolBar 是否需要添加溺忧,再使用相應(yīng)方法 - addToolbarIfRequired 實(shí)現(xiàn)這一目的。

這個(gè)方法會(huì)根據(jù)根視圖上 UITextField 的數(shù)量執(zhí)行對(duì)應(yīng)的代碼盯孙,下面為一般情況下執(zhí)行的代碼:

- (void)addToolbarIfRequired {
    NSArray *siblings = [self responderViews];
    for (UITextField *textField in siblings) {
        [textField addPreviousNextDoneOnKeyboardWithTarget:self previousAction:@selector(previousAction:) nextAction:@selector(nextAction:) doneAction:@selector(doneAction:) shouldShowPlaceholder:_shouldShowTextFieldPlaceholder];
        textField.inputAccessoryView.tag = kIQPreviousNextButtonToolbarTag;

        IQToolbar *toolbar = (IQToolbar*)[textField inputAccessoryView];
        toolbar.tintColor = [UIColor blackColor];
        [toolbar setTitle:textField.drawingPlaceholderText];
        [textField setEnablePrevious:NO next:YES];
    }
}

在鍵盤上的 IQToolBar 一般由三部分組成:

  • 切換 UITextField 的箭頭按鈕
  • 指示當(dāng)前 UITextField 的 placeholder
  • Done Button
IQToolBarIte

這些 item 都是 IQBarButtonItem 的子類

這些 IQBarButtonItem 以及 IQToolBar 都是通過方法 - addPreviousNextDoneOnKeyboardWithTarget:previousAction:nextAction:doneAction: 或者類似方法添加的:

- (void)addPreviousNextDoneOnKeyboardWithTarget:(id)target previousAction:(SEL)previousAction nextAction:(SEL)nextAction doneAction:(SEL)doneAction titleText:(NSString*)titleText {
    IQBarButtonItem *prev = [[IQBarButtonItem alloc] initWithImage:imageLeftArrow style:UIBarButtonItemStylePlain target:target action:previousAction];
    IQBarButtonItem *next = [[IQBarButtonItem alloc] initWithImage:imageRightArrow style:UIBarButtonItemStylePlain target:target action:nextAction];
    IQTitleBarButtonItem *title = [[IQTitleBarButtonItem alloc] initWithTitle:self.shouldHideTitle?nil:titleText];
    IQBarButtonItem *doneButton =[[IQBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:target action:doneAction];

    IQToolbar *toolbar = [[IQToolbar alloc] init];
    toolbar.barStyle = UIBarStyleDefault;
    toolbar.items = @[prev, next, title, doneButton];
    toolbar.titleInvocation = self.titleInvocation;
    [(UITextField*)self setInputAccessoryView:toolbar];
}

上面是方法簡化后的實(shí)現(xiàn)代碼鲁森,初始化需要的 IQBarButtonItem,然后將這些 IQBarButtonItem 全部加入到 IQToolBar 上镀梭,最后設(shè)置 UITextFieldaccessoryView刀森。

保存 frame

這一步的主要目的是為了在鍵盤隱藏時(shí)恢復(fù)到原來的狀態(tài),其實(shí)現(xiàn)也非常簡單:

_rootViewController = [_textFieldView topMostController];
_topViewBeginRect = _rootViewController.view.frame;

獲取 topMostController报账,在 _topViewBeginRect 中保存 frame研底。

adjustFrame

在上述的任務(wù)都完成之后,最后就需要調(diào)用 - adjustFrame 方法來調(diào)整當(dāng)前根試圖控制器的 frame 了:

我們只會(huì)研究一般情況下的實(shí)現(xiàn)代碼透罢,因?yàn)檫@個(gè)方法大約有 400 行代碼對(duì)不同情況下的實(shí)現(xiàn)有不同的路徑榜晦,包括有 lastScrollView、含有 superScrollView 等等羽圃。

而這里會(huì)省略絕大多數(shù)情況下的實(shí)現(xiàn)代碼乾胶。

- (void)adjustFrame {
    UIWindow *keyWindow = [self keyWindow];
    UIViewController *rootController = [_textFieldView topMostController];    
    CGRect textFieldViewRect = [[_textFieldView superview] convertRect:_textFieldView.frame toView:keyWindow];
    CGRect rootViewRect = [[rootController view] frame];
    CGSize kbSize = _kbSize;
    kbSize.height += keyboardDistanceFromTextField;
    CGFloat topLayoutGuide = CGRectGetHeight(statusBarFrame);
    CGFloat move = MIN(CGRectGetMinY(textFieldViewRect)-(topLayoutGuide+5), CGRectGetMaxY(textFieldViewRect)-(CGRectGetHeight(keyWindow.frame)-kbSize.height));
    
    if (move >= 0) {
        rootViewRect.origin.y -= move;
        [self setRootViewFrame:rootViewRect];
    } else {
        CGFloat disturbDistance = CGRectGetMinY(rootViewRect)-CGRectGetMinY(_topViewBeginRect);
        if (disturbDistance < 0) {
            rootViewRect.origin.y -= MAX(move, disturbDistance);
            [self setRootViewFrame:rootViewRect];
        }
    }
}

方法 - adjustFrame 的工作分為兩部分:

  1. 計(jì)算 move 的距離

  2. 調(diào)用 - setRootViewFrame: 方法設(shè)置 rootView 的大小

    - (void)setRootViewFrame:(CGRect)frame {
        UIViewController *controller = [_textFieldView topMostController];    
        frame.size = controller.view.frame.size;
    
        [UIView animateWithDuration:_animationDuration delay:0 options:(_animationCurve|UIViewAnimationOptionBeginFromCurrentState) animations:^{
            [controller.view setFrame:frame];
        } completion:NULL];
    }
    

不過,在 - textFieldViewDidBeginEditing: 的調(diào)用棧中朽寞,并沒有執(zhí)行 - setRootViewFrame: 來更新視圖的大小识窿,因?yàn)辄c(diǎn)擊最上面的 UITextField 時(shí),不需要移動(dòng)視圖就能保證鍵盤不會(huì)遮擋 UITextField脑融。

keyboardWillShow:

上面的代碼都是在鍵盤出現(xiàn)之前執(zhí)行的喻频,而這里的 - keyboardWillShow: 方法的目的是為了保證鍵盤出現(xiàn)之后,依然沒有阻擋 UITextField肘迎。

因?yàn)槊恳粋€(gè) UITextField 對(duì)應(yīng)的鍵盤大小可能不同甥温,所以,這里通過檢測鍵盤大小是否改變妓布,來決定是否調(diào)用 - adjustFrame 方法更新視圖的大小姻蚓。

- (void)keyboardWillShow:(NSNotification*)aNotification {
    _kbShowNotification = aNotification;
    
    _animationCurve = [[aNotification userInfo][UIKeyboardAnimationCurveUserInfoKey] integerValue];
    _animationCurve = _animationCurve<<16;
    CGFloat duration = [[aNotification userInfo][UIKeyboardAnimationDurationUserInfoKey] floatValue];
    if (duration != 0.0)    _animationDuration = duration;
    
    CGSize oldKBSize = _kbSize;
    CGRect kbFrame = [[aNotification userInfo][UIKeyboardFrameEndUserInfoKey] CGRectValue];
    CGRect screenSize = [[UIScreen mainScreen] bounds];
    CGRect intersectRect = CGRectIntersection(kbFrame, screenSize);

    if (CGRectIsNull(intersectRect)) {
        _kbSize = CGSizeMake(screenSize.size.width, 0);
    } else {
        _kbSize = intersectRect.size;
    }
 
    if (!CGSizeEqualToSize(_kbSize, oldKBSize)) {
        [self adjustFrame];
    }
}

- adjustFrame 方法調(diào)用之前,執(zhí)行了很多代碼都是用來保存一些關(guān)鍵信息的匣沼,比如通知對(duì)象狰挡、動(dòng)畫曲線、動(dòng)畫時(shí)間。

最關(guān)鍵的是更新鍵盤的大小圆兵,然后比較鍵盤的大小 CGSizeEqualToSize(_kbSize, oldKBSize) 來判斷是否執(zhí)行 - adjustFrame 方法跺讯。

因?yàn)?- adjustFrame 方法的結(jié)果是依賴于鍵盤大小的,所以這里對(duì) - adjustFrame 是有意義并且必要的殉农。

鍵盤的隱藏

通過點(diǎn)擊 IQToolBar 上面的 done 按鈕刀脏,鍵盤就會(huì)隱藏:

IQKeyboardManager-hide-keyboard

鍵盤隱藏的過程中會(huì)依次調(diào)用下面的三個(gè)方法:

  • - keyboardWillHide:
  • - textFieldViewDidEndEditing:
  • - keyboardDidHide:
IQKeyboardManager: ****** keyboardWillHide: started ******
IQKeyboardManager: Restoring <UINavigationController 0x7fbaa4009e00> frame to : {{0, 0}, {320, 568}}
IQKeyboardManager: ****** keyboardWillHide: ended ******
IQKeyboardManager: ****** textFieldViewDidEndEditing: started ******
IQKeyboardManager: ****** textFieldViewDidEndEditing: ended ******
IQKeyboardManager: ****** keyboardDidHide: started ******
IQKeyboardManager: ****** keyboardDidHide: ended ******

鍵盤在收起時(shí),需要將視圖恢復(fù)至原來的位置超凳,而這也就是 - keyboardWillHide: 方法要完成的事情:

[strongSelf.rootViewController.view setFrame:strongSelf.topViewBeginRect]

并不會(huì)給出該方法的全部代碼愈污,只會(huì)給出關(guān)鍵代碼梳理它的工作流程。

在重新設(shè)置視圖的大小以及位置之后轮傍,會(huì)對(duì)之前保存的屬性進(jìn)行清理:

_lastScrollView = nil;
_kbSize = CGSizeZero;
_startingContentInsets = UIEdgeInsetsZero;
_startingScrollIndicatorInsets = UIEdgeInsetsZero;
_startingContentOffset = CGPointZero;

而之后調(diào)用的兩個(gè)方法 - textFieldViewDidEndEditing: 以及 - keyboardDidHide: 也只做了很多簡單的清理工作暂雹,包括添加到 window 上的手勢,并重置保存的 UITextField 和視圖的大小创夜。

- (void)textFieldViewDidEndEditing:(NSNotification*)notification{
    [_textFieldView.window removeGestureRecognizer:_tapGesture];
    _textFieldView = nil;
}

- (void)keyboardDidHide:(NSNotification*)aNotification {
    _topViewBeginRect = CGRectZero;
}

UITextField 和 UITextView 通知機(jī)制

因?yàn)榭蚣艿墓δ苁腔谕ㄖ獙?shí)現(xiàn)的杭跪,所以通知的時(shí)序至關(guān)重要,在 IQKeyboardManagerConstants.h 文件中詳細(xì)地描述了在編輯 UITextField 的過程中驰吓,通知觸發(fā)的先后順序涧尿。

notification-IQKeyboardManage

上圖準(zhǔn)確說明了通知發(fā)出的時(shí)機(jī),透明度為 50% 的部分表示該框架沒有監(jiān)聽這個(gè)通知檬贰。

UITextView 的通知機(jī)制與 UITextField 略有不同:

UITextView-Notification-IQKeyboardManage

當(dāng) Begin Editing 這個(gè)事件發(fā)生時(shí)姑廉,UITextView 的通知機(jī)制會(huì)先發(fā)出 UIKeyboardWillShowNotification 通知,而 UITextField 會(huì)先發(fā)出 UITextFieldTextDidBeginEditingNotification 通知翁涤。

而這兩個(gè)通知的方法都調(diào)用了 - adjustFrame 方法來更新視圖的大小桥言,最開始我并不清楚到底是為什么?直到我給作者發(fā)了一封郵件葵礼,作者告訴我這么做的原因:

Good questions draveness. I'm very happy to answer your questions. There is a file in library IQKeyboardManagerConstants.h. You can find iOS Notification mechanism structure.

You'll find that for UITextField, textField notification gets fire first and then UIKeyboard notification fires.

For UITextView, UIKeyboard notification gets fire first and then UITextView notification get's fire.

So that's why I have to call adjustFrame at both places to fulfill both situations. But now I think I should add some validation and make sure to call it once to improve performance.

Let me know if you have some more questions, I would love to answer them. Thanks again to remind me about this issue.

在不同方法中調(diào)用通知的原因是号阿,UITextView 和 UITextField 通知機(jī)制的不同,不過作者可能會(huì)在未來的版本中修復(fù)這一問題鸳粉,來獲得性能上的提升倦西。

小結(jié)

IQKeyboardManager 使用通知機(jī)制來解決鍵盤遮擋輸入框的問題,因?yàn)槭褂昧朔诸惒⑶以?IQKeyboardManager+ load 方法中激活了框架的使用赁严,所以達(dá)到了零行代碼解決這一問題的效果。

雖然 IQKeyboardManager 很好地解決了這一問題粉铐、為我們帶來了良好的體驗(yàn)疼约。不過,由于其涉及 UI 層級(jí)蝙泼;并且需要考慮非常多的邊界以及特殊條件程剥,框架的代碼不是很容易閱讀,但是這不妨礙 IQKeyboardManager 成為非常優(yōu)秀的開源項(xiàng)目。

關(guān)注倉庫织鲸,及時(shí)獲得更新:iOS-Source-Code-Analyze

Follow: Draveness · Github

原文鏈接: http://draveness.me/keyboard/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末舔腾,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子搂擦,更是在濱河造成了極大的恐慌稳诚,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瀑踢,死亡現(xiàn)場離奇詭異扳还,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)橱夭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門氨距,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人棘劣,你說我怎么就攤上這事俏让。” “怎么了茬暇?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵首昔,是天一觀的道長。 經(jīng)常有香客問我而钞,道長沙廉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任臼节,我火速辦了婚禮撬陵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘网缝。我一直安慰自己巨税,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布粉臊。 她就那樣靜靜地躺著草添,像睡著了一般。 火紅的嫁衣襯著肌膚如雪扼仲。 梳的紋絲不亂的頭發(fā)上远寸,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音屠凶,去河邊找鬼驰后。 笑死,一個(gè)胖子當(dāng)著我的面吹牛矗愧,可吹牛的內(nèi)容都是我干的灶芝。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼夜涕!你這毒婦竟也來了犯犁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤女器,失蹤者是張志新(化名)和其女友劉穎酸役,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體晓避,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡簇捍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了俏拱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片暑塑。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖锅必,靈堂內(nèi)的尸體忽然破棺而出事格,到底是詐尸還是另有隱情,我是刑警寧澤搞隐,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布驹愚,位于F島的核電站,受9級(jí)特大地震影響劣纲,放射性物質(zhì)發(fā)生泄漏逢捺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一癞季、第九天 我趴在偏房一處隱蔽的房頂上張望劫瞳。 院中可真熱鬧,春花似錦绷柒、人聲如沸志于。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽伺绽。三九已至,卻和暖如春嗜湃,著一層夾襖步出監(jiān)牢的瞬間奈应,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工购披, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留钥组,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓今瀑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子橘荠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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