在 iOS 的项目中,经常会遇到内嵌网页的场景,而网页内的输入框也比较常见。一般来说,键盘的弹起事件和防止遮挡输入框是由前端同学处理的,但可能因为种种原因,这个锅可能甩到我们 iOS 工程师身上。
锅从天而降,不接也没法。那就逆向思维,分析一下具体的实现思路吧:
期望结果: 键盘弹出后,webView 需要上移某一个距离,保证不遮挡输入框。(如何获取正确的距离?)
↓
获取距离: 计算输入框距离屏幕底部和键盘高度之间的差值。(输入框在 WebView 中,如何获取?)
↓
获取输入框的位置: 给视图添加一个全屏的点击手势,点击输入框的时候,通过坐标转换,计算出相对屏幕的点击坐标。(但点击位置并不一定是输入框的底部,所以键盘弹出后依然会遮住一部分输入框,获取 WebView 输入框的高度即可)
↓
获取输入框的高度: 通过注入一段获取输入框高度的 JS 代码,来尝试获取 input 元素的高度。
有了方案,就开始动手吧…
一、基础准备
添加点击手势 ,监听系统键盘弹出事件, 并禁调 WebView 键盘弹出事件。
- (void)loadView{
[super loadView];
UITapGestureRecognizer *webTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(webTap:)];
webTap.delegate = self;
webTap.cancelsTouchesInView = NO;
[self.view addGestureRecognizer:webTap];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
#pragma mark - UIGestureRecognizerDelegate
//允许多个手势识别器共同识别
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
//禁止网页键盘的响应
- (UIView*)viewForZoomingInScrollView:(UIScrollView*)scrollView{
return nil;
}
二、捕捉点击事件,进行坐标转换
考虑到网页里底部输入框的情况,默认会避开安全区,所以做一个DefineBlankHeight(34) 的宽容机制。
- (void)webTap:(UITapGestureRecognizer *)sender{
CGPoint tapPoint = [sender locationInView:self.view];
CGPoint convertPoint = [self.view.window convertPoint:tapPoint fromView:self.view];
self.gapHeight = SCREEN_HEIGHT - convertPoint.y - DefineBlankHeight;
}
三、JS 注入
网页加载完成的时候注入 JS 方法
- (void)webViewDidFinishLoad:(UIWebView *)webView{
NSString *js = @"function getInputHeight() { \
return document.activeElement.style.height;\
}";
NSString *result= [webView stringByEvaluatingJavaScriptFromString:js];
NSLog(@"%@",result);
}
这里需要说明一下,通过document.activeElement.style.height
只能获取到标签的内部属性,比如:
<input type="text" id="msg-area" style="width:80%;height:25px;">
但对于下面这种:
.textfield {
height: 50px;
color: #00FF00;
font-size: 16px;
}
<input class="textfield" type="text" name="submit" value="1" class="text1"><br>
就获取不到了,具体方法还得请教一下前端的小老弟。
四、实现键盘监听的方法
// 调用注入的 js 方法,获取到输入框高度
-(void)inputTapAction{
NSString *result = [self.webView stringByEvaluatingJavaScriptFromString:@"getInputHeight()"];
NSLog(@"inputTapAction %@",result);
self.inputHeight = [[result componentsSeparatedByString:@"px"].firstObject intValue];
//如果获取不到,给一个默认值。
if (self.inputHeight == 0) {
self.inputHeight = 35;
}
}
//键盘将要出现
- (void)keyboardWillShow:(NSNotification *)notification {
[self inputTapAction];
NSDictionary *userInfo = [notification userInfo];
NSValue* value = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
CGRect keyboardRect = [value CGRectValue];
CGFloat finalHeight = keyboardRect.size.height - self.gapHeight + self.inputHeight;
[self.webView mas_updateConstraints:^(MASConstraintMaker *make) {
make.top.inset(-finalHeight);
make.bottom.inset(finalHeight);
}];
CGFloat keyboardDuration =
[userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
[UIView animateWithDuration:keyboardDuration animations:^{
[self.webView.superview layoutIfNeeded];
}];
}
//键盘将要消失
- (void)keyboardWillHide:(NSNotification *)notification {
[self.webView mas_updateConstraints:^(MASConstraintMaker *make) {
make.top.inset(0);
make.bottom.inset(0);
}];
NSDictionary *userInfo = [notification userInfo];
CGFloat keyboardDuration =
[userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
[UIView animateWithDuration:keyboardDuration animations:^{
[self.webView.superview layoutIfNeeded];
}];
}
最后,别忘了移除观察
- (void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
总结:主要原理是就是通过输入框相对屏幕位置和键盘之间的高度差,来改变 webView 的 frame。核心代码就是手势的穿透、坐标转换 和 js 方法的注入。