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的强大之处,可随意修改私有类,私有方法(当然也要不影响方法本身功能,慎用)

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • ——读《人工智能的未来》后感 未来以来,值得期待。近几十年来,人工智能经历着从爆发到寒冬再到野蛮生长的历程,随着人...
    卓木鸟在思考阅读 3,196评论 0 2
  • 今天,我很愉快。 一大早起床,第一件事就是把昨晚整理好的文章《望峨台补记》在简书上发了,并投了几个专题。我不再投《...
    无为而字阅读 3,826评论 12 14
  • 从前,你说中了我的毒,只有我可解。现在,你中了手机的毒,无药可解。 1 周末出门吃饭,隔壁桌坐着一对小情侣,等菜间...
    浮笙留白阅读 3,427评论 0 0
  • 对不起,凤姐!如果那天真像你所说的那样我就像两个人那么我也不用这么说,因为我直接说就可以了。 ... ...
    泣灵_162b阅读 1,549评论 0 0

友情链接更多精彩内容