UIWebview让editable html切换键盘不失焦

最近在做app时,需要内置一个富文本编辑器,调研之后采用webView +editable html方案,写demo时看起来挺简单,毫不犹豫得开始搞,却发现在真正集成进app时却踩坑不少,真是啪啪打脸~

需求如下:在键盘上方,点击按钮时可以随意切换到各种选择区,并保证webView不失焦,如下图所示:

调研过程:

1. 最先想到得方案便是,点击按钮,关闭键盘,然后弹出自定义试图。

比如表情选择器。这种方案当然可行,但比较麻烦,当选择完一个表情,此时html已经处于失焦状态,是没办法直接输入进html的,当然你可以记录失焦前的位置,然后使用document.exeCommand方法硬插入,同时还有一个问题是,切换试图效果并不流畅,会有键盘关闭得过程,本来直接就可以输入的,就因为切换键盘不能输了,忍痛舍弃。

2. 接下来的想法便是,如何能让切换键盘时不失焦。查看官方文档获得如下知识

2.1 keboard实际上是一个inputView,作为UIResponder类的属性,每一个子类都是可以定制它的,只要重新定义inputView property为读写属性,并重写getter方法即可,系统本身可定制的UITextField和UITextView就是这么干的

This property is typically used to provide a view to replace the system-supplied keyboard that is presented for UITextField and UITextView objects.

The value of this read-only property is nil. A responder object that requires a custom view to gather input from the user should redeclare this property as read-write and use it to manage its custom input view. When the receiver becomes the first responder, the responder infrastructure presents the specified input view automatically.

2.2 若当前试图为第一响应者,可以调用UIResponder的 reloadInputViews方法,强制刷新inputView以及inputAccessView。

You can use this method to refresh the custom input view or input accessory view associated with the current object when it is the first responder. The views are replaced immediately—that is, without animating them into place. If the current object is not the first responder, this method has no effect.

2.3 有了如上知识,思路就有了,那修改UIWebView的inputView就可以了,操作之后发现,键盘是可以自由切换得,但是失焦了,那也就是说输入状态,webView不是第一响应者,那肯定是内部得子试图了,最终通过代码打印第一响应者,发现其是一个叫UIWebBrowser的东西,这货是继承于UIView的,但是是内部私有类,怎么办?browser信息如下


UIWebBrowser description
3. 是时候使用runtime了。

3.1 要么替换掉UIWebBrowser类为我们自定义类;要么替换该类的inputView getter方法,然后在切换切换键盘时修改inputView即可,我采用第一种方案,这也符合苹果官方文档的做法,核心代码如下:

- (void)_hackWebBrowserClass{
    // 此处想法是暂存最开始的inputView,实际证明并不需要
   // if ([UIWebViewHackishlyViewManager sharedInstance].originalKeyboardView == nil){
  //      [UIWebViewHackishlyViewManager sharedInstance].originalKeyboardView = self.inputView;
 //   }
    
    UIView *browserView = [self browserView];
    Class hackClass = objc_getClass(hackishFixClassName);
    // 如果还未生成自定义class,则生成;就是说还没替换
    if (!hackClass) {
        hackClass = [self _hackishSubclassExists];
    }
    if (![browserView isMemberOfClass:hackClass]){
        if (hackClass){
            object_setClass(browserView, hackClass);
        }
    }
}
// 生成自定义类,其继承于UIWebBrowser类
- (Class)_hackishSubclassExists {
    if (objc_getClass(hackishFixClassName)) return objc_getClass(hackishFixClassName);
    Class newClass = objc_allocateClassPair([[self browserView] class], hackishFixClassName, 0);
    
// 添加两个getter方法;使用replace应该也是可以的,可以试试
    IMP accessoryViewImp = [self methodForSelector:@selector(changedInputAccessoryView)];
    class_addMethod(newClass, @selector(inputAccessoryView), accessoryViewImp, "@@:");
    
    IMP inputViewImp = [self methodForSelector:@selector(changedInputView)];
    class_addMethod(newClass, @selector(inputView), inputViewImp, "@@:");
    objc_registerClassPair(newClass);
    
    return newClass;
}

3.2 中间绕了一个弯。当需要切换回系统键盘时,本来想把UIWebBrowser的实现替换为系统本身得实现,但是这么干的话,可恶的inputAccessryView也随着键盘一块儿出来了,所以不能把类替换回去,那怎么办呢?最终采用保存原始的inputView方式,当需要切换回系统键盘时,替换回去 【后来证明不需要这么做,如下代码多余了】

代码如下:


- (UIView*)changedInputView{

    UIView *view = [UIWebViewHackishlyViewManager sharedInstance].inputView;

    if(view)returnview;
    
    return [UIWebViewHackishlyViewManager sharedInstance].originalKeyboardView;

}

完整demo待整理后发出

小总结:

经此一役,再次感受到runtime的强大之处,可随意修改私有类,私有方法(当然也要不影响方法本身功能,慎用)

你可能感兴趣的:(UIWebview让editable html切换键盘不失焦)