longxdragon's blog

跟Keyboard说Good Bye

iOS开发中,键盘事件一直是一件让开发者头疼的事,它不像安卓开发那样,系统会自动把输入框往上推,而iOS开发则不同,它需要开发者自己计算高度,并根据相应的需求去移动对应的View,这样的过程,机械重复,但又无法避免。过去的我,一直这样,在坑里爬着。。。

首先,我们来看看与键盘事件有关的几个需求:
1、页面有一个输入框,输入文字时,键盘弹出,如果挡着输入框,则需要把输入框或整个背景View往上推到合适的位置。
2、页面有好多输入框时,键盘应该不能挡着对应输入源的输入框。
3、页面的输入框可能不是单一层级,可能处于更深层级的View上,即View->View->…->输入框的层级关系时,以上需求依旧。

好吧!当初我看到这样的需求时,我的反应是很简单,不就是很正常的步骤:
1、在ViewController中注册键盘的监听。
2、弹出键盘,根据键盘的高度和输入框的底部来判断,是否把View往上移动,以及移动多少。
3、输入框多的话,多加个判断,就是找出那个输入框正在处于被输入状态(isFirstResponder)。
4、。。。。。。

写着写着开始有点后怕了,那么多页面呢?我总不能每次都这么算吧?而且还有第3种那样的需求,计算的程度肯定更复杂。虽然大多数不复杂的界面,Copy下代码,改改就可以了!但懒惰的我,望而却步,我要跟这种方式说byebye!!!

废话不多说,直接上代码:

1
[[LXKeyboardNotification defaultNotification] addKeyboardNotificationForSuperView:self.view superViewTopMargin:0];

只需要这样一句话,你就可以轻松的实现键盘挡出输入框的问题啦,是不是发现很简单?!
这里为了跟ViewController脱离关系,我创建了一个管理类LXKeyboardNotification,注册、移除键盘监听也在这里去完成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
- (instancetype)init {
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardShowNotify:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardHideNotify:) name:UIKeyboardWillHideNotification object:nil];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}

因为可能会有多个输入框,键盘弹出的时候并不知道哪个输入框处于被输入的状态,我们可以遍历找出,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (UIView *)getFirstResponderAtView:(UIView *)view {
for (UIView *subView in view.subviews) {
if (subView.isFirstResponder) {
return subView;
}
}
// 没有找到,继续子view寻找
for (UIView *subView in view.subviews) {
UIView *firstResponderView = [self getFirstResponderAtView:subView];
if (firstResponderView && firstResponderView.isFirstResponder) {
return firstResponderView;
}
}
return nil;
}

找到对应的输入框后,还需要计算出该输入框的底部距离需要移动View的顶部的距离(即该输入框相对于移动View的位置)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- (CGFloat)caculateAbsoluteBottomY:(UIView *)view {
CGFloat bottomY = CGRectGetMaxY(view.frame);
UIView *subView = view;
while (subView.superview != _superView) {
subView = subView.superview;
// 如果是滚动视图,应该计算偏移量
if ([subView isKindOfClass:[UIScrollView class]]) {
bottomY -= ((UIScrollView *)subView).contentOffset.y;
}
bottomY += subView.frame.origin.y;
}
//
// 这边添加 _superViewTopMargin 为 传入的 _superView 距离整个屏幕的上边距
// 因为此处的计算是否上移,是根整个屏幕的高度进行比较的
// 并且 _superView 的原始Y位置也有可能不为0
// 还有可能 _superView 还有好几层的superView
// 这边也需要考虑是否是滚动视图
//
bottomY += _superViewTopMargin;
if ([_superView isKindOfClass:[UIScrollView class]]) {
bottomY -= ((UIScrollView *)_superView).contentOffset.y;
}
return bottomY;
}

最后再配合屏幕的高度、移动View距离屏幕顶部的距离来确定是否移动已经移动的距离。

1
2
3
4
5
6
7
8
9
10
11
12
13
UIView *firstResponderView = [self getFirstResponderAtView:_superView];
if (firstResponderView) {
CGFloat bottomY = [self caculateAbsoluteBottomY:firstResponderView]
if (bottomY + keyboardSize.height > [UIScreen mainScreen].bounds.size.height) {
CGFloat length = [UIScreen mainScreen].bounds.size.height - (bottomY + keyboardSize.height);
[UIView animateWithDuration:duration animations:^{
CGRect frame = _superView.frame;
frame.origin.y = _superOriginFrame.origin.y + length;
_superView.frame = frame;
}];
}
}

至此,复杂而又繁琐的键盘事件,就这么轻松的解决了!告别重复的Copy代码、手动的计算高度吧!
这边是源码LXKeyboardNotification
可能还有一些情况没考虑进去,希望读者多提意见,一起讨论。