1. 方案简述
键盘遮挡输入框的处理,主要是依靠UIKeyboardWillShowNotification(键盘出现)和UIKeyboardWillHideNotification(键盘消失)的通知来实现的。
通过UIKeyboardWillShowNotification.userInfo[UIKeyboardFrameEndUserInfoKey],可以获得键盘出现后的frame,该frame是相对于窗口基坐标系(window base coordinates)的frame。因此,将输入框的frame转换到相同的坐标系,然后比较键盘的frame和输入框转换后的frame,就可以获得页面需要上移的距离。
2. 方案实现
(1) 添加变量
@interface ViewController ()
/// 页面未上移前的高度
@property(assign, nonatomic) CGFloat viewOriginY;
@end
(1) 添加通知
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// 保存页面未上移前的高度
self.viewOriginY = self.view.frame.origin.y;
// 添加键盘弹出/消失的通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShowNotification:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHideNotification:) name:UIKeyboardWillHideNotification object:nil];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
// 移除键盘弹出/消失的通知
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}
(2) 通知事件处理
/// 键盘弹出的通知
- (void)keyboardWillShowNotification:(NSNotification *)sender {
// 获取键盘弹出后的Rect
NSValue *keyBoardEndFrame = [sender userInfo][UIKeyboardFrameEndUserInfoKey];
CGRect endRect = [keyBoardEndFrame CGRectValue];
// 获得响应的TextField
UITextField *textField = [self textFieldWithFirstResponderInThisView];
if (textField) {
// 计算输入框相对于window base coordinates(nil)的坐标
CGRect textFieldFrame = [textField convertRect:textField.bounds toView:nil];
// 计算输入框下边的位置和键盘上边的差值
CGFloat occlusionHeight = textFieldFrame.origin.y + textFieldFrame.size.height - endRect.origin.y;
if (occlusionHeight > 0) {
// 当差值大于0时,说明键盘遮挡了输入框,需要将self.view上移
[UIView animateWithDuration:0.25f animations:^{
self.view.frame = CGRectMake(0, self.viewOriginY - occlusionHeight, self.view.frame.size.width, self.view.frame.size.height);
}];
} else if (self.view.frame.origin.y != self.viewOriginY) {
// 若self.view移动过,则复原self.view的位置
[UIView animateWithDuration:0.25f animations:^{
self.view.frame = CGRectMake(0, self.viewOriginY, self.view.frame.size.width, self.view.frame.size.height);
}];
}
}
}
/// 键盘消失的通知
- (void)keyboardWillHideNotification:(NSNotification *)sender {
if (self.view.frame.origin.y != self.viewOriginY) {
[UIView animateWithDuration:0.25f animations:^{
self.view.frame = CGRectMake(0, self.viewOriginY, self.view.frame.size.width, self.view.frame.size.height);
}];
}
}
(3) 点击空白处收起键盘
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 点击空白处,退出键盘
[self.view endEditing:YES];
}
(4) 当页面存在多个输入框时,获得第一响应者
/// 获得当前页面响应的输入框
- (UITextField *)textFieldWithFirstResponderInThisView {
// 当知道是哪个TextField被响应时,就可以不需要使用递归来获取第一响应者
return (UITextField *) [self findFirstResponder:self.view];
}
/// 通过递归获得第一响应者
- (UIView *)findFirstResponder:(UIView *)view {
if ([view isFirstResponder]) {
return view;
}
for (UIView *subView in view.subviews) {
UIView *firstResponder = [self findFirstResponder:subView];
if (firstResponder) {
return firstResponder;
}
}
return nil;
}