一、键盘遮挡的场景分类

1. 开始页面录入。输入控件在屏幕的下部,键盘出现后遮挡输入控件

2. 切换焦点。新输入框被当前键盘部分遮挡,可点击

3. 切换输入法。

4. 屏幕旋转。屏幕高度发生变化,原未被遮挡输入框旋转后被遮挡

 

 

二、UI需上移的距离计算

计算控件底部与键盘终点顶部的距离,调整阀值自定。通常选择输入控件最近的UIViewController->view作为同一参照

NSDictionary *userInfo = [notification userInfo];
            NSValue* aValue        = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
            CGRect keyboardRect = [aValue CGRectValue];
            keyboardRect        = [self.view convertRect:keyboardRect fromView:nil];
            CGFloat keyboardTop = keyboardRect.origin.y;
 
            CGFloat margin = 20;//文本框距键盘顶边最小距离
            
            CGRect textFieldFrame = [self.viewconvertRect:_textFieldCall4Adjust.framefromView: _textFieldCall4Adjust.superview];
            CGFloat textFieldBottom = textFieldFrame.origin.y + textFieldFrame.size.height;
 
            CGFloat delta = textFieldBottom  + margin - keyboardTop;

三、获取键盘的动画时间和时间函数

UIKeyboardAnimationCurveUserInfoKey
UIKeyboardAnimationDurationUserInfoKey
 
	[UIView setAnimationCurve:[[[notif userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
        [UIView animateWithDuration:[[[notif userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue] animations:^(){

四、UI上移调整方式

1.调整最上层UIViewController->view的frame. 最上层VC定义与转屏控制中的最上层VC相同,如UITableViewController,UINavigationControlller

 

2. 输入控件在UIScrollView或其子类,调整contentSizeH + contentOffsetY

3. 输入控件在UIScrollView或其子类,调整contentInsetB + contentOffsetY

 

注意:1.  单独调整contetntOffset以偏移视图的状态是不可靠的,页面手动滑动会弹回contentSize内。

         2. 有时单独调整contentInset能够触发contentOffset的变化,如更改contentInsetT时视图上移, offset增加。

               有文章说,单独设置contentInsetB会使contentOffset自动增加,然而Demo测试中未呈现该结果。

         3.  键盘隐藏下移时,单独设置contentSize或contentInset, 恢复原contentSize或置contentInsetB为0即可

 

五、相关系统通知的触发

监听通知:

UIKeyboardWillShowNotification, UIKeyboardDidShowNotification, UIKeyboardWillHideNotification, UIKeyboardDidHideNotification, 
UIKeyboardWillChangeFrameNotification, UIKeyboardDidChangeFrameNotification, UIApplicationWillChangeStatusBarOrientationNotification, UIApplicationDidChangeStatusBarOrientationNotification

键盘使用系统默认全键盘之中文,英文,数字加符号

 

 

iOS7

iOS8

iOS9

1. 新获得焦点

UIKeyboardWillChangeFrameNotification

UIKeyboardWillShowNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidShowNotification

UIKeyboardWillChangeFrameNotification

UIKeyboardWillShowNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidShowNotification


UIKeyboardWillChangeFrameNotification

UIKeyboardWillShowNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidShowNotification


2.失去焦点

UIKeyboardWillChangeFrameNotification

UIKeyboardWillHideNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidHideNotification

UIKeyboardWillChangeFrameNotification

UIKeyboardWillHideNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidHideNotification

UIKeyboardWillChangeFrameNotification

UIKeyboardWillHideNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidHideNotification

3.切换焦点

无(新旧键盘相同或等高)

无(新旧键盘相同或等高)

无(新旧键盘相同或等高)

4.更换英文至中文输入法,并输入中文字符使得键盘增高

UIKeyboardWillChangeFrameNotification

UIKeyboardWillShowNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidShowNotification

UIKeyboardWillChangeFrameNotification

UIKeyboardWillShowNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidShowNotification


(开启输入预测增加一个循环)

UIKeyboardWillChangeFrameNotification

UIKeyboardWillShowNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidShowNotification

UIKeyboardWillChangeFrameNotification

UIKeyboardWillShowNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidShowNotification


 

(切换时无,即时开闭输入预测代入一次循环)

UIKeyboardWillChangeFrameNotification

UIKeyboardWillShowNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidShowNotification

 

5.更换中文(有多一行)至英文

UIKeyboardWillChangeFrameNotification

UIKeyboardWillShowNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidShowNotification

UIKeyboardWillChangeFrameNotification

UIKeyboardWillShowNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidShowNotification


(开启输入预测增加一个循环)

UIKeyboardWillChangeFrameNotification

UIKeyboardWillShowNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidShowNotification

UIKeyboardWillChangeFrameNotification

UIKeyboardWillShowNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidShowNotification


 

(切换时无,即时开闭输入预测代入一次循环)

UIKeyboardWillChangeFrameNotification

UIKeyboardWillShowNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidShowNotification

6.转屏

UIKeyboardWillChangeFrameNotification

UIKeyboardWillHideNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidHideNotification

UIApplicationWillChangeStatusBarOrientationNotification

UIApplicationDidChangeStatusBarOrientationNotification

UIKeyboardWillChangeFrameNotification

UIKeyboardWillShowNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidShowNotification


(willShow的时候键盘为旧转向frame)

  didShow时键盘为新转向frame

UIApplicationWillChangeStatusBarOrientationNotification

UIApplicationDidChangeStatusBarOrientationNotification

UIKeyboardWillChangeFrameNotification

UIKeyboardWillHideNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidHideNotification

UIKeyboardWillChangeFrameNotification

UIKeyboardWillShowNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidShowNotification

UIKeyboardWillChangeFrameNotification

UIKeyboardWillHideNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidHideNotification

UIKeyboardWillChangeFrameNotification

UIKeyboardWillShowNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidShowNotification

(输入预测开启无关)

UIApplicationWillChangeStatusBarOrientationNotification

UIKeyboardWillChangeFrameNotification

UIKeyboardWillHideNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidHideNotification

UIApplicationDidChangeStatusBarOrientationNotification

UIKeyboardWillChangeFrameNotification

UIKeyboardWillShowNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidShowNotification

UIKeyboardWillChangeFrameNotification

UIKeyboardWillShowNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidShowNotification

7.滑动隐藏键盘

UIKeyboardWillChangeFrameNotification

UIKeyboardWillHideNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidHideNotification

UIKeyboardWillChangeFrameNotification

UIKeyboardWillHideNotification

UIKeyboardWillChangeFrameNotification

UIKeyboardWillHideNotification


UIKeyboardDidChangeFrameNotification

UIKeyboardDidHideNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidHideNotification

UIKeyboardWillChangeFrameNotification

UIKeyboardWillHideNotification

UIKeyboardWillChangeFrameNotification

UIKeyboardWillHideNotification

 

UIKeyboardDidChangeFrameNotification

UIKeyboardDidHideNotification

UIKeyboardDidChangeFrameNotification

UIKeyboardDidHideNotification

结论:1. willShow, didShow, willHide,didHide覆盖了所有通知存在Case

           2. willShow,willHide为最佳同步键盘动画时机。但为适配iOS67屏. 选择willShow, didShow, willHide三个键盘通知作键盘适应事件处理入口
           3. 切换焦点需另作处理获取事件处理时机
           4. 转屏时,willShow, didShow, willHide事件,如将动画合并提交,可能会不符合预想

           5. 其它:人为触发endEditing 和 keyboardDismissMode设为UIScrollViewKeyboardDismissModeOnDrag的差异,人为触发时可以将retain的输入控件释放, UIScrollViewKeyboardDismissModeOnDrag时,不便于获取置nil时机。

             iOS8,9 UIScrollViewKeyboardDismissModeOnDrag时,会触发两次keyboardXXXHide通知    

 

六、处理方案

 

1. 获取切换焦点时机

a. VC保持每次新获取焦点的输入控件。输入控件将要开始编辑时,根据新输入控件和旧有实例判断是否需调整键盘。

 

SuperClass:
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
    // 在子类中应调用该方法
    if (_textFieldFocusing && _textFieldFocusing.isFirstResponder && _textFieldFocusing != textField ) {
        // 处理切换输入焦点不会发出键盘SHOW通知的问题
        _textFieldFocusing = textField;
        [self p_handleKeyboardShow:nil];
    } else {
        _textFieldFocusing = textField;
    }
    return YES;
}
SubClass:
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField{
    if (textField.tag == TagCouldNotEdit) {
        return NO;
    }
 
    return [super textFieldShouldBeginEditing:textField];;
 
}

   b.切换焦点时,无法获取键盘frame,因此需保存最近一次键盘通知

 

notification = notification?:oldKeyBoardNoti;
    if (!notification) {
        return;
    }
 
    oldKeyBoardNoti = notification;

2. 键盘处理事件串行化处理

   每次动画提交同步至上次动画结束。

 

dispatch_semaphore_t semaphore;
 
    dispatch_queue_t queue;
。。。。
    semaphore = dispatch_semaphore_create(1);
 
    queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 
- (void) keyboardDidShow:(NSNotification *) notif {
    if (!self.textFieldFocusing || ![self.textFieldFocusingisFirstResponder]) {
        return;
    }
    // 转屏支持
    // iphone7, ios4转屏时只走一遍hide->show,而且willShow时键盘尺寸异常
    [self p_handleKeyboardShow:notif];
}
 
-(void)keyboardWillShow:(NSNotification *)notification {
    if (!self.textFieldFocusing || ![self.textFieldFocusingisFirstResponder]) {
        return;
    }
    [self p_handleKeyboardShow:notification];
}
- (void) p_handleKeyboardShow:(NSNotification *)notification {
    
    notification = notification?:oldKeyBoardNoti;
    if (!notification) {
        return;
    }
    oldKeyBoardNoti = notification;
    
    // 第一次键盘出现,且未获取tableView的contentSize时,获取tableview的size
    // 最多只执行一次
    if (_idealTableViewSizeHeight == 0) {
        _idealTableViewSizeHeight = self.tableView.contentSize.height;
    }
    
    // 动画块
    void (^changeContentOffset)() = ^(){
        [UIView setAnimationCurve:[[[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
        [UIView animateWithDuration:([[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue]?:0.2) animations:^(){
            // @IMPORTANT 每次block动画任务被执行时,重新计算移动距离
            // 需操作的环境参数
            NSDictionary *userInfo = [notification userInfo];
            NSValue* aValue        = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
            CGRect keyboardRect = [aValue CGRectValue];
            keyboardRect        = [self.view convertRect:keyboardRect fromView:nil];
            CGFloat keyboardTop = keyboardRect.origin.y;
            CGFloat margin = 20;//文本框距键盘顶边最小距离
            
            CGRect textFieldFrame = [self.viewconvertRect:_textFieldFocusing.framefromView:_textFieldFocusing.superview];
            CGFloat textFieldBottom = textFieldFrame.origin.y + textFieldFrame.size.height;
            CGFloat delta = textFieldBottom  + margin - keyboardTop;
            
            CGFloat contentSizeHeight = MAX( self.tableView.frame.size.height, self.tableView.contentSize.height);
            [self.tableViewsetContentSize:CGSizeMake(self.view.frame.size.width, contentSizeHeight + delta)];
            [self.tableView setContentOffset: CGPointMake(0, delta + oldTableViewOffset.y) animated:NO];
        } completion:^(BOOL finished) {
            dispatch_async( queue, ^{
                dispatch_semaphore_signal(semaphore);
            });
        }];
    };
    
    // 串行动画控制
    dispatch_async(queue, ^{
        
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        
        // 需操作的环境参数
        NSDictionary *userInfo = [notification userInfo];
        NSValue* aValue        = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
        CGRect keyboardRect = [aValue CGRectValue];
        keyboardRect        = [self.view convertRect:keyboardRect fromView:nil];
        CGFloat keyboardTop = keyboardRect.origin.y;
        CGFloat margin = 20;//文本框距键盘顶边最小距离
        
        CGRect textFieldFrame = [self.viewconvertRect:_textFieldFocusing.framefromView:_textFieldFocusing.superview];
        CGFloat textFieldBottom = textFieldFrame.origin.y + textFieldFrame.size.height;
        
        if (   !self.textFieldFocusing
            || ![self.textFieldFocusing isFirstResponder]
            || textFieldBottom  + margin  <= keyboardTop
            || fabs(textFieldBottom  + margin - keyboardTop) < 0.1) {
            // 不需要执行动画
            dispatch_semaphore_signal(semaphore);
        } else {
            oldTableViewOffset = self.tableView.contentOffset;
            dispatch_async(dispatch_get_main_queue(), ^{
                changeContentOffset();
            });
        }
    });
}
 
- (void) keyboardWillHide:(NSNotification *) notif {
    // 通过drag隐藏时,会两次发出键盘隐藏通知,但对UI目前没有影响,不做原因查找
    // 动画块
    void (^changeContentOffset)() = ^(){
        self.tableView.bounces = NO;
        [UIView setAnimationCurve:[[[notif userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
        [UIView animateWithDuration:[[[notif userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue] animations:^(){
            [self.tableViewsetContentSize:CGSizeMake(self.view.frame.size.width,_idealTableViewSizeHeight)];
        } completion:^(BOOL finished) {
            self.tableView.bounces = YES;
            dispatch_async(queue, ^{
                dispatch_semaphore_signal(semaphore);
            });
        }] ;
    };
    // 串行动画控制
    dispatch_async( queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        if (self.tableView.contentSize.height <_idealTableViewSizeHeight
            || fabs(self.tableView.contentSize.height - _idealTableViewSizeHeight)<0.1) {
            // 不需要执行动画
            dispatch_semaphore_signal(semaphore);
        } else {
            dispatch_async(dispatch_get_main_queue(), ^{
                changeContentOffset();
            });
        }
    });  
 
}

七、自定义键盘

1.iputView

注意,当inputView的ViewController自定义,inputView的相关响应方法写在ViewController时,使用三方键盘可能发生异常

禁用三方键盘方法

- (BOOL)application:(UIApplication *)application shouldAllowExtensionPointIdentifier:(NSString *)extensionPointIdentifier
{
    if ([extensionPointIdentifier isEqualToString: UIApplicationKeyboardExtensionPointIdentifier])
    {
        return NO;
    }
    return YES;
 
}

2.UIInputViewController

UIResponder的inputViewController为readOnly,需要强制改为readWrite
@interface TextField:UITextField
 
@property (nonatomic, readwrite, strong) UIInputViewController*inputViewController;
 
@end
 
@implementation TextField
 
 
@end

 

3.Application Extension

先新建Application,然后新建customed Keyboard Target, XCode将生成基础代码。开发时不能用Interface Builder, 必须全部代码手写