国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 系統(tǒng) > iOS > 正文

iOS中表情鍵盤的完整實(shí)現(xiàn)方法詳解

2020-07-26 02:27:01
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

前言

最近在公司做了個(gè)表情鍵盤的需求,這個(gè)需求的技術(shù)難度不會(huì)很大,比較偏向業(yè)務(wù)。但是要把用戶體驗(yàn)做的好也是不容易的,其中有幾個(gè)點(diǎn)需要特別注意。話不多說(shuō),下面開(kāi)始正文(注:本文對(duì)應(yīng)的Demo放在Github上:https://github.com/VernonVan/PPStickerKeyboard (本地上傳) )。

市面上的表情鍵盤的分析

首先來(lái)看一下市面上主要的幾個(gè)APP上的表情鍵盤,平時(shí)使用的時(shí)候不會(huì)去關(guān)注細(xì)節(jié),這次特意去使用了表情鍵盤,發(fā)現(xiàn)各個(gè)APP的體驗(yàn)還是有優(yōu)有劣的。

首先是QQ和微信,這兩者差不多,切換到表情鍵盤的時(shí)候都是沒(méi)有光標(biāo)的,這樣的用戶體驗(yàn)是非常不好的,沒(méi)有辦法在輸入表情的時(shí)候框選區(qū)域,也不能拖動(dòng)光標(biāo)進(jìn)行特定位置的復(fù)制黏貼刪除等操作,微信甚至在輸入框里顯示的都不是點(diǎn)擊的表情圖片,而是文字描述。

微信QQ表情鍵盤.JPG

接下來(lái)看一下微博國(guó)際版,國(guó)際版調(diào)起表情鍵盤時(shí)是有光標(biāo)的,是一個(gè)"真正的"鍵盤,但是想要拖拽光標(biāo)的時(shí)候,很大概率上會(huì)觸發(fā)到保存圖片的行為(如下圖所示),導(dǎo)致根本沒(méi)辦法拖動(dòng)光標(biāo)。


微博國(guó)際版誤觸.JPG

同時(shí)微博國(guó)際版輸入框表情黏貼后的光標(biāo)定位是錯(cuò)誤的,如下圖,開(kāi)始時(shí)光標(biāo)是在第4個(gè)表情后面,然后復(fù)制狗頭+害羞兩個(gè)表情黏貼到光標(biāo)后,光標(biāo)還是在第4個(gè)表情后,同時(shí)黏貼的表情前后都莫名多了空格。


微博國(guó)際版黏貼.JPG

最后是微博,微博客戶端的表情鍵盤的體驗(yàn)是非常好的,上面說(shuō)到的問(wèn)題都不存在,而且表情鍵盤的刪除按鈕還能長(zhǎng)按刪除輸入框的內(nèi)容。


微博表情鍵盤.jpg

表情鍵盤的實(shí)現(xiàn)

實(shí)現(xiàn)效果

主要實(shí)現(xiàn)了以下幾個(gè)功能

  • 能輸入表情,有光標(biāo),支持復(fù)制黏貼刪除表情等
  • 長(zhǎng)按預(yù)覽表情
  • 刪除表情、長(zhǎng)按連續(xù)刪除表情
  • 適配 iPhone X


演示.GIF

基本思路

首先,表情包的圖片是用bundle的形式組織的,用PPSticker類表征一套表情包,用PPEmoji類表征某一個(gè)表情,用一個(gè)plist作為配置文件,存儲(chǔ)表情包的信息。

表情的組織.jpg

PPStickerDataManager類主要負(fù)責(zé)數(shù)據(jù)部分,用單例的形式,這樣可以在初始化的時(shí)候只會(huì)讀取一次plist文件中的所有表情信息;同時(shí)我們把輸入框內(nèi)容發(fā)到服務(wù)端以及從服務(wù)端請(qǐng)求到的都是純文本的,比如會(huì)把 "笑死了🤣" 轉(zhuǎn)成 "笑死了[笑哭]" 這樣的純文本,而不是直接把表情圖片直接發(fā)到服務(wù)端,也就是說(shuō)項(xiàng)目中有大量的地方會(huì)有把文本->表情的操作,所以PPStickerDataManager類也提供匹配某段純文本中的表情,并把文本替換為圖片的功能,PPStickerDataManager類的頭文件如下:

@interface PPStickerDataManager : NSObject+ (instancetype)sharedInstance;/// 所有的表情包@property (nonatomic, strong, readonly) NSArray<PPSticker *> *allStickers;/* 匹配給定attributedString中的所有emoji,如果匹配到的emoji有本地圖片的話會(huì)直接換成本地的圖片** @param attributedString 可能包含表情包的attributedString* @param font 表情圖片的對(duì)齊字體大小*/- (void)replaceEmojiForAttributedString:(NSMutableAttributedString *)attributedString font:(UIFont *)font;@end

"真正的"鍵盤

真正的鍵盤也就是說(shuō)調(diào)起表情鍵盤時(shí)輸入框是有光標(biāo)的,能進(jìn)行拖拽光標(biāo)、選中區(qū)域等的操作,這樣的體驗(yàn)才是與系統(tǒng)鍵盤一致的。其實(shí)系統(tǒng)已經(jīng)提供好了接口給我們直接使用,UITextView和UITextField都有的inputView和inputAccessoryView就是用來(lái)實(shí)現(xiàn)自定義鍵盤的,這兩個(gè)屬性的定義如下:

// 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;

同時(shí)系統(tǒng)鍵盤在 設(shè)置->聲音->按鍵音 選項(xiàng)打開(kāi)且手機(jī)非靜音狀態(tài)下輸入是有按鍵的聲音的,這個(gè)按鍵音也是可以支持的,只要自定義鍵盤類遵循UIInputViewAudioFeedback協(xié)議,同時(shí)實(shí)現(xiàn) enableInputClicksWhenVisible方法并返回YES,這樣就可以在點(diǎn)擊表情的時(shí)候調(diào)用[[UIDevice currentDevice] playInputClick]方法發(fā)出按鍵音了,詳情請(qǐng)查看蘋果的官方文檔。

下面是Demo中鍵盤切換方法的實(shí)現(xiàn):

- (void)changeKeyboardTo:(PPKeyboardType)toType{ switch (toType) { case PPKeyboardTypeSystem:  self.textView.inputView = nil; // 切換到系統(tǒng)鍵盤  [self.textView reloadInputViews]; // 調(diào)用reloadInputViews方法會(huì)立刻進(jìn)行鍵盤的切換  break; case PPKeyboardTypeSticker:    self.textView.inputView = self.stickerKeyboard; // 切換到自定義的表情鍵盤  [self.textView reloadInputViews];  break; default:  break; }}

去除表情的拖拽交互

在iOS11上,UITextView上的NSTextAttachment(表情)默認(rèn)可以進(jìn)行拖拽交互,但是卻導(dǎo)致拖動(dòng)光標(biāo)時(shí)很容易觸發(fā)這個(gè)交互(圖示可以查看上面說(shuō)到的微博國(guó)際版中的誤觸)。一番查找之后才找到一個(gè)比較隱蔽的屬性:textDragInteraction,直接設(shè)置為NO就能禁止掉NSTextAttachment的拖拽交互。

if (@available(iOS 11.0, *)) { // 只在iOS11及以上才有這個(gè)屬性 _textView.textDragInteraction.enabled = NO;}

與服務(wù)端的交互

我們?cè)谳斎肟蛑休斎氲膬?nèi)容與服務(wù)端進(jìn)行交互的時(shí)候都是用純文本的,比如會(huì)把 "笑死了🤣" 轉(zhuǎn)成 "笑死了[笑哭]" 這樣的純文本發(fā)到服務(wù)端,而不是直接發(fā)表情圖片,向服務(wù)端請(qǐng)求內(nèi)容的時(shí)候也是傳回 "笑死了[笑哭]",然后客戶端再根據(jù)正則匹配找出表情替換成對(duì)應(yīng)的表情圖片,然后顯示到頁(yè)面上。具體過(guò)程可以看下圖:


與服務(wù)端的交互.png

也就是說(shuō),我們?cè)O(shè)置到輸入框的NSAttributedString中的每一個(gè)NSTextAttachment都有一個(gè)"隱藏的"屬性―表情的文本描述,這里對(duì)NSAttributedString進(jìn)行拓展就能實(shí)現(xiàn)。pp_setTextBackedString可以對(duì)NSAttributedString的指定range設(shè)置一個(gè)PPTextBackedString類型的屬性,而pp_plainTextForRange能拿到NSAttributedString指定range的純文本。具體實(shí)現(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

靈活的光標(biāo)

表情功能,UITextView都是用NSAttributedString進(jìn)行賦值的,并且我們底層其實(shí)還是用上面說(shuō)到的純文本進(jìn)行實(shí)現(xiàn)的,那么把 [笑死] 轉(zhuǎn)成 🤣 就會(huì)從4個(gè)字符變成1個(gè)字符,這里是有差值的,如果不處理的話就會(huì)出現(xiàn)上面提到的微博國(guó)際版中復(fù)制黏貼輸入框的表情會(huì)導(dǎo)致光標(biāo)位置不對(duì),甚至莫名其妙多出前后空格的問(wèn)題。為了精準(zhǔn)的定位光標(biāo),我們需要自行處理好這些問(wèn)題。

這里自己繼承并實(shí)現(xiàn)了UITextView的子類PPStickerTextView,在這個(gè)類中重載復(fù)制、黏貼、剪切等操作,分別對(duì)應(yīng)的方法如下:

- (void)cut:(id)sender; // 剪切- (void)copy:(id)sender; // 復(fù)制- (void)paste:(id)sender; // 黏貼

下面以剪切方法舉例,看看怎么處理光標(biāo)的問(wèn)題,需要注意的地方請(qǐng)看對(duì)應(yīng)的注釋:

- (void)cut:(id)sender{ // 1.從textView中拿到對(duì)應(yīng)的純文本,比如:笑死了[笑死] NSString *string = [self.attributedText pp_plainTextForRange:self.selectedRange]; if (string.length) { // 2. 將純文本寫入到剪貼板中 [UIPasteboard generalPasteboard].string = string; // 3. 記住當(dāng)前的光標(biāo)位置 NSRange selectedRange = self.selectedRange; NSMutableAttributedString *attributeContent = [[NSMutableAttributedString alloc] initWithAttributedString:self.attributedText]; // 4. 將檢測(cè)到是表情的文本替換成對(duì)應(yīng)的圖片 [attributeContent replaceCharactersInRange:self.selectedRange withString:@""]; self.attributedText = attributeContent;  // 5. 重新設(shè)置光標(biāo) self.selectedRange = NSMakeRange(selectedRange.location, 0); }}

技術(shù)點(diǎn)的分析就是以上這些,詳細(xì)的代碼可以到Github上clone下來(lái)查看:https://github.com/VernonVan/PPStickerKeyboard (本地上傳)

總結(jié)

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)武林網(wǎng)的支持。

發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 濮阳市| 越西县| 普格县| 新建县| 永善县| 罗定市| 英山县| 临江市| 伊春市| 珲春市| 武安市| 广平县| 霸州市| 栖霞市| 贺兰县| 兴城市| 玉山县| 隆回县| 正安县| 射洪县| 宝兴县| 蒙城县| 富阳市| 金坛市| 和顺县| 基隆市| 台湾省| 惠州市| 桃源县| 永吉县| 古浪县| 博白县| 扎兰屯市| 麦盖提县| 信丰县| 荆门市| 攀枝花市| 宽甸| 开阳县| 绥中县| 师宗县|