iOS中表情鍵盤的完整實現(xiàn)方法詳解
前言
最近在公司做了個表情鍵盤的需求,這個需求的技術難度不會很大,比較偏向業(yè)務。但是要把用戶體驗做的好也是不容易的,其中有幾個點需要特別注意。話不多說,下面開始正文(注:本文對應的Demo放在Github上:https://github.com/VernonVan/PPStickerKeyboard (本地上傳) )。
市面上的表情鍵盤的分析
首先來看一下市面上主要的幾個APP上的表情鍵盤,平時使用的時候不會去關注細節(jié),這次特意去使用了表情鍵盤,發(fā)現(xiàn)各個APP的體驗還是有優(yōu)有劣的。
首先是QQ和微信,這兩者差不多,切換到表情鍵盤的時候都是沒有光標的,這樣的用戶體驗是非常不好的,沒有辦法在輸入表情的時候框選區(qū)域,也不能拖動光標進行特定位置的復制黏貼刪除等操作,微信甚至在輸入框里顯示的都不是點擊的表情圖片,而是文字描述。

微信QQ表情鍵盤.JPG
接下來看一下微博國際版,國際版調(diào)起表情鍵盤時是有光標的,是一個"真正的"鍵盤,但是想要拖拽光標的時候,很大概率上會觸發(fā)到保存圖片的行為(如下圖所示),導致根本沒辦法拖動光標。

微博國際版誤觸.JPG
同時微博國際版輸入框表情黏貼后的光標定位是錯誤的,如下圖,開始時光標是在第4個表情后面,然后復制狗頭+害羞兩個表情黏貼到光標后,光標還是在第4個表情后,同時黏貼的表情前后都莫名多了空格。

微博國際版黏貼.JPG
最后是微博,微博客戶端的表情鍵盤的體驗是非常好的,上面說到的問題都不存在,而且表情鍵盤的刪除按鈕還能長按刪除輸入框的內(nèi)容。

微博表情鍵盤.jpg
表情鍵盤的實現(xiàn)
實現(xiàn)效果
主要實現(xiàn)了以下幾個功能
- 能輸入表情,有光標,支持復制黏貼刪除表情等
- 長按預覽表情
- 刪除表情、長按連續(xù)刪除表情
- 適配 iPhone X

演示.GIF
基本思路
首先,表情包的圖片是用bundle的形式組織的,用PPSticker類表征一套表情包,用PPEmoji類表征某一個表情,用一個plist作為配置文件,存儲表情包的信息。

表情的組織.jpg
PPStickerDataManager類主要負責數(shù)據(jù)部分,用單例的形式,這樣可以在初始化的時候只會讀取一次plist文件中的所有表情信息;同時我們把輸入框內(nèi)容發(fā)到服務端以及從服務端請求到的都是純文本的,比如會把 "笑死了🤣" 轉(zhuǎn)成 "笑死了[笑哭]" 這樣的純文本,而不是直接把表情圖片直接發(fā)到服務端,也就是說項目中有大量的地方會有把文本->表情的操作,所以PPStickerDataManager類也提供匹配某段純文本中的表情,并把文本替換為圖片的功能,PPStickerDataManager類的頭文件如下:
@interface PPStickerDataManager : NSObject + (instancetype)sharedInstance; /// 所有的表情包 @property (nonatomic, strong, readonly) NSArray<PPSticker *> *allStickers; /* 匹配給定attributedString中的所有emoji,如果匹配到的emoji有本地圖片的話會直接換成本地的圖片 * * @param attributedString 可能包含表情包的attributedString * @param font 表情圖片的對齊字體大小 */ - (void)replaceEmojiForAttributedString:(NSMutableAttributedString *)attributedString font:(UIFont *)font; @end
"真正的"鍵盤
真正的鍵盤也就是說調(diào)起表情鍵盤時輸入框是有光標的,能進行拖拽光標、選中區(qū)域等的操作,這樣的體驗才是與系統(tǒng)鍵盤一致的。其實系統(tǒng)已經(jīng)提供好了接口給我們直接使用,UITextView和UITextField都有的inputView和inputAccessoryView就是用來實現(xiàn)自定義鍵盤的,這兩個屬性的定義如下:
// Presented when object becomes first responder. If set to nil, reverts to following responder chain. If // set while first responder, will not take effect until reloadInputViews is called. @property (nullable, readwrite, strong) UIView *inputView; @property (nullable, readwrite, strong) UIView *inputAccessoryView;
同時系統(tǒng)鍵盤在 設置->聲音->按鍵音 選項打開且手機非靜音狀態(tài)下輸入是有按鍵的聲音的,這個按鍵音也是可以支持的,只要自定義鍵盤類遵循UIInputViewAudioFeedback協(xié)議,同時實現(xiàn) enableInputClicksWhenVisible方法并返回YES,這樣就可以在點擊表情的時候調(diào)用[[UIDevice currentDevice] playInputClick]方法發(fā)出按鍵音了,詳情請查看蘋果的官方文檔。
下面是Demo中鍵盤切換方法的實現(xiàn):
- (void)changeKeyboardTo:(PPKeyboardType)toType
{
switch (toType) {
case PPKeyboardTypeSystem:
self.textView.inputView = nil; // 切換到系統(tǒng)鍵盤
[self.textView reloadInputViews]; // 調(diào)用reloadInputViews方法會立刻進行鍵盤的切換
break;
case PPKeyboardTypeSticker:
self.textView.inputView = self.stickerKeyboard; // 切換到自定義的表情鍵盤
[self.textView reloadInputViews];
break;
default:
break;
}
}
去除表情的拖拽交互
在iOS11上,UITextView上的NSTextAttachment(表情)默認可以進行拖拽交互,但是卻導致拖動光標時很容易觸發(fā)這個交互(圖示可以查看上面說到的微博國際版中的誤觸)。一番查找之后才找到一個比較隱蔽的屬性:textDragInteraction,直接設置為NO就能禁止掉NSTextAttachment的拖拽交互。
if (@available(iOS 11.0, *)) { // 只在iOS11及以上才有這個屬性
_textView.textDragInteraction.enabled = NO;
}
與服務端的交互
我們在輸入框中輸入的內(nèi)容與服務端進行交互的時候都是用純文本的,比如會把 "笑死了🤣" 轉(zhuǎn)成 "笑死了[笑哭]" 這樣的純文本發(fā)到服務端,而不是直接發(fā)表情圖片,向服務端請求內(nèi)容的時候也是傳回 "笑死了[笑哭]",然后客戶端再根據(jù)正則匹配找出表情替換成對應的表情圖片,然后顯示到頁面上。具體過程可以看下圖:

與服務端的交互.png
也就是說,我們設置到輸入框的NSAttributedString中的每一個NSTextAttachment都有一個"隱藏的"屬性—表情的文本描述,這里對NSAttributedString進行拓展就能實現(xiàn)。pp_setTextBackedString可以對NSAttributedString的指定range設置一個PPTextBackedString類型的屬性,而pp_plainTextForRange能拿到NSAttributedString指定range的純文本。具體實現(xiàn)如下:
@implementation NSAttributedString (PPAddition)
- (NSString *)pp_plainTextForRange:(NSRange)range
{
if (range.location == NSNotFound || range.length == NSNotFound) {
return nil;
}
NSMutableString *result = [[NSMutableString alloc] init];
if (range.length == 0) {
return result;
}
NSString *string = self.string;
[self enumerateAttribute:PPTextBackedStringAttributeName inRange:range options:kNilOptions usingBlock:^(id value, NSRange range, BOOL *stop) {
PPTextBackedString *backed = value;
if (backed && backed.string) {
[result appendString:backed.string];
} else {
[result appendString:[string substringWithRange:range]];
}
}];
return result;
}
@end
@implementation NSMutableAttributedString (PPAddition)
- (void)pp_setTextBackedString:(PPTextBackedString *)textBackedString range:(NSRange)range
{
if (textBackedString && ![NSNull isEqual:textBackedString]) {
[self addAttribute:PPTextBackedStringAttributeName value:textBackedString range:range];
} else {
[self removeAttribute:PPTextBackedStringAttributeName range:range];
}
}
@end
靈活的光標
表情功能,UITextView都是用NSAttributedString進行賦值的,并且我們底層其實還是用上面說到的純文本進行實現(xiàn)的,那么把 [笑死] 轉(zhuǎn)成 🤣 就會從4個字符變成1個字符,這里是有差值的,如果不處理的話就會出現(xiàn)上面提到的微博國際版中復制黏貼輸入框的表情會導致光標位置不對,甚至莫名其妙多出前后空格的問題。為了精準的定位光標,我們需要自行處理好這些問題。
這里自己繼承并實現(xiàn)了UITextView的子類PPStickerTextView,在這個類中重載復制、黏貼、剪切等操作,分別對應的方法如下:
- (void)cut:(id)sender; // 剪切 - (void)copy:(id)sender; // 復制 - (void)paste:(id)sender; // 黏貼
下面以剪切方法舉例,看看怎么處理光標的問題,需要注意的地方請看對應的注釋:
- (void)cut:(id)sender
{
// 1.從textView中拿到對應的純文本,比如:笑死了[笑死]
NSString *string = [self.attributedText pp_plainTextForRange:self.selectedRange];
if (string.length) {
// 2. 將純文本寫入到剪貼板中
[UIPasteboard generalPasteboard].string = string;
// 3. 記住當前的光標位置
NSRange selectedRange = self.selectedRange;
NSMutableAttributedString *attributeContent = [[NSMutableAttributedString alloc] initWithAttributedString:self.attributedText];
// 4. 將檢測到是表情的文本替換成對應的圖片
[attributeContent replaceCharactersInRange:self.selectedRange withString:@""];
self.attributedText = attributeContent;
// 5. 重新設置光標
self.selectedRange = NSMakeRange(selectedRange.location, 0);
}
}
技術點的分析就是以上這些,詳細的代碼可以到Github上clone下來查看:https://github.com/VernonVan/PPStickerKeyboard (本地上傳)
總結
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關文章
iOS中UIWebView網(wǎng)頁加載組件的基礎及使用技巧實例
UIWebView是開發(fā)中很常用的應用內(nèi)調(diào)用網(wǎng)頁瀏覽的控件,這里整理了一些iOS中UIWebView網(wǎng)頁加載組件的基礎及使用技巧實例 ,需要的朋友可以參考下2016-06-06
iOS開發(fā)中使用UILabel設置字體的相關技巧小結
這篇文章主要介紹了iOS開發(fā)中UILabel設置字體的相關技巧小結,代碼基于傳統(tǒng)的Objective-C,需要的朋友可以參考下2016-01-01
iOS10最新實現(xiàn)遠程通知的開發(fā)教程詳解
這篇文章主要介紹了iOS10最新遠程通知開發(fā)的實現(xiàn)過程,文章先對推送通知以及遠程推送通知等進行了基本介紹,然后通過示例代碼詳細介紹了iOS10 全新遠程通知的教程,有需要的朋友們可以參考借鑒,下面來一起看看吧。2016-09-09

