一、键盘遮挡的场景分类
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, 必须全部代码手写