最近在做app時(shí)亡哄,需要內(nèi)置一個(gè)富文本編輯器,調(diào)研之后采用webView +editable html方案,寫demo時(shí)看起來挺簡單讯榕,毫不猶豫得開始搞,卻發(fā)現(xiàn)在真正集成進(jìn)app時(shí)卻踩坑不少匙睹,真是啪啪打臉~
需求如下:在鍵盤上方愚屁,點(diǎn)擊按鈕時(shí)可以隨意切換到各種選擇區(qū),并保證webView不失焦痕檬,如下圖所示:
調(diào)研過程:
1. 最先想到得方案便是霎槐,點(diǎn)擊按鈕,關(guān)閉鍵盤梦谜,然后彈出自定義試圖丘跌。
比如表情選擇器。這種方案當(dāng)然可行唁桩,但比較麻煩碍岔,當(dāng)選擇完一個(gè)表情,此時(shí)html已經(jīng)處于失焦?fàn)顟B(tài)朵夏,是沒辦法直接輸入進(jìn)html的蔼啦,當(dāng)然你可以記錄失焦前的位置,然后使用document.exeCommand方法硬插入仰猖,同時(shí)還有一個(gè)問題是捏肢,切換試圖效果并不流暢,會(huì)有鍵盤關(guān)閉得過程饥侵,本來直接就可以輸入的鸵赫,就因?yàn)榍袚Q鍵盤不能輸了,忍痛舍棄躏升。
2. 接下來的想法便是辩棒,如何能讓切換鍵盤時(shí)不失焦。查看官方文檔獲得如下知識
2.1 keboard實(shí)際上是一個(gè)inputView膨疏,作為UIResponder類的屬性一睁,每一個(gè)子類都是可以定制它的,只要重新定義inputView property為讀寫屬性佃却,并重寫getter方法即可者吁,系統(tǒng)本身可定制的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 若當(dāng)前試圖為第一響應(yīng)者,可以調(diào)用UIResponder的 reloadInputViews方法饲帅,強(qiáng)制刷新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就可以了育八,操作之后發(fā)現(xiàn)对途,鍵盤是可以自由切換得,但是失焦了髓棋,那也就是說輸入狀態(tài)实檀,webView不是第一響應(yīng)者,那肯定是內(nèi)部得子試圖了仲锄,最終通過代碼打印第一響應(yīng)者劲妙,發(fā)現(xiàn)其是一個(gè)叫UIWebBrowser的東西湃鹊,這貨是繼承于UIView的儒喊,但是是內(nèi)部私有類,怎么辦币呵?browser信息如下
3. 是時(shí)候使用runtime了怀愧。
3.1 要么替換掉UIWebBrowser類為我們自定義類;要么替換該類的inputView getter方法余赢,然后在切換切換鍵盤時(shí)修改inputView即可芯义,我采用第一種方案,這也符合蘋果官方文檔的做法妻柒,核心代碼如下:
- (void)_hackWebBrowserClass{
// 此處想法是暫存最開始的inputView扛拨,實(shí)際證明并不需要
// 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);
// 添加兩個(gè)getter方法;使用replace應(yīng)該也是可以的央渣,可以試試
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 中間繞了一個(gè)彎计盒。當(dāng)需要切換回系統(tǒng)鍵盤時(shí),本來想把UIWebBrowser的實(shí)現(xiàn)替換為系統(tǒng)本身得實(shí)現(xiàn)芽丹,但是這么干的話北启,可惡的inputAccessryView也隨著鍵盤一塊兒出來了,所以不能把類替換回去拔第,那怎么辦呢咕村?最終采用保存原始的inputView方式,當(dāng)需要切換回系統(tǒng)鍵盤時(shí)蚊俺,替換回去 【后來證明不需要這么做培廓,如下代碼多余了】
代碼如下:
- (UIView*)changedInputView{
UIView *view = [UIWebViewHackishlyViewManager sharedInstance].inputView;
if(view)returnview;
return [UIWebViewHackishlyViewManager sharedInstance].originalKeyboardView;
}
完整demo待整理后發(fā)出
小總結(jié):
經(jīng)此一役,再次感受到runtime的強(qiáng)大之處春叫,可隨意修改私有類肩钠,私有方法(當(dāng)然也要不影響方法本身功能泣港,慎用)