iOS 仿微信的表情鍵盤

最近公司項目要求重構(gòu)表情鍵盤近范,之前項目中表情鍵盤的控件結(jié)構(gòu)底層是UIScrollview你稚,考慮到如果用戶的自定義表情添加的過多困肩,對性能會有一定的影響,最終決定將底部的Container換成UICollectionview吐句,并添加類似微信表情鍵盤的長按預(yù)覽试和、發(fā)送功能猜嘱,在自定義表情區(qū)域處,如果自定義表情的頁數(shù)超過2頁嫁艇,底部添加滑塊朗伶,并且當(dāng)頁面顯示在非自定義表情頁時,滑塊處于隱藏狀態(tài)步咪,類似于微信论皆。

先對于本demo整體圖層結(jié)構(gòu)進行一下梳理,方便后續(xù)內(nèi)容的理解猾漫,見下圖!
  • 紅色區(qū)域是TMSStickerView
  • 藍(lán)色區(qū)域是UIcollectionViewCell点晴,每個cell的model依據(jù)屏幕尺寸已經(jīng)預(yù)先處理成TMSEmoji,通過該model中的emojis屬性悯周,來顯示當(dāng)前cell所需要展示的所有數(shù)據(jù)
  • 綠色區(qū)域是cell上的自定義view(TMSEmojiItem)粒督,長按預(yù)覽的手勢添加于該視圖上
@interface TMSEmoji : NSObject

@property (assign, nonatomic) TMSEmojiType type;
@property (strong, nonatomic) NSString *title;
@property (strong, nonatomic) NSArray *emojis;

@end

開發(fā)中遇到的問題:

1. 系統(tǒng)emoji表情頁與自定義表情頁交替時,底部滑塊的隱藏與顯示
2. 手指從自定義表情頁拖拽到emoji表情頁禽翼,在不松手的情況下屠橄,又拖回自定義表情頁時,底部滑塊的隱藏與顯示
處理方案:
  • 何時對滑塊做顯隱動畫捐康?
    既然是交替時的特殊處理仇矾,首先在- (void)scrollViewDidScroll:(UIScrollView *)scrollView方法中依據(jù)collectionview當(dāng)前偏移量從dataSource中取出當(dāng)前展示的模型,以及下一個模型解总。獲得兩個模型后贮匕,首先判斷兩個表情模型(TMSEmoji)的類型是否相同,如果不同花枫,則需要對滑塊添加顯隱的動畫刻盐。

  • 對滑塊是做顯示動畫還是隱藏動畫?
    當(dāng)collectionview的contentOffset.x逐漸變大時劳翰,此時需要對滑塊做顯示動畫敦锌;反之對其做隱藏動畫。這里需要辨別出collectionview的滑動方向是左滑還是滑佳簸。
    并且針對于上述問題2中的情形乙墙,需要在- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView方法中對于當(dāng)前顯示的item根據(jù)類型判斷來控制滑塊動畫是否執(zhí)行。

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    
    //    if (self.lastContentOffsetX < scrollView.contentOffset.x) {
    //        NSLog(@"向左滾動");
    //    }else{
    //        NSLog(@"向右滾動");
    //    }
    
    CGFloat fIdx = (scrollView.contentOffset.x + SCREEN_WIDTH * 0.2) / SCREEN_WIDTH;
    NSInteger index = (NSInteger)fIdx;
    if (index >= self.dataSource.count) {
        return;
    }
    
    TMSEmoji *emojiModel = self.dataSource[index];
    [self.toolBar setCurrentSelectedTooItemWithEmojiString:emojiModel.title];
    if (index+1 >= self.dataSource.count) {
        if (emojiModel.type == TMSEmojiTypeCustom) {
            [self.emojiSlider setValue:(scrollView.contentOffset.x / SCREEN_WIDTH - self.customEmojiStartIndex) animated:YES];
        }
        return;
    }
    
    TMSEmoji *nextEmoji = self.dataSource[index+1];
    
    if (emojiModel.type == nextEmoji.type) {
        if (emojiModel.type == TMSEmojiTypeCustom) {
            [self.emojiSlider setValue:(scrollView.contentOffset.x / SCREEN_WIDTH - self.customEmojiStartIndex) animated:YES];
        }
        return;
    }
    
    if (nextEmoji.type == TMSEmojiTypeCustom) {
        
        if (self.isAnimationing) {
            return;
        }
        
        // 左滑 消失
        /*
         右滑 顯示
         1.在custom上右滑
         2.從people上右滑
         */
        if (self.lastContentOffsetX > scrollView.contentOffset.x) { // 右滑
            
            if (self.emojiSlider.alpha != 0) {
                
                [self sliderAnimationWithState:NO];
            }
            
        } else {
            
            if (scrollView.contentOffset.x >= (self.customEmojiStartIndex - 0.3) * SCREEN_WIDTH) {
                
                if (self.emojiSlider.alpha != 1) {
                    
                    self.emojiSlider.alpha = 1;
                    
                    [self sliderAnimationWithState:YES];
                    
                }
            }
        }
    }
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    
    if (self.hideCustomEmoji || self.emojiSlider.hidden) {
        return;
    }
    
    NSInteger index = scrollView.contentOffset.x / SCREEN_WIDTH;
    
    TMSEmoji *emoji = self.dataSource[index];
    if (emoji.type == TMSEmojiTypeCustom) {
        
        self.customSelectedIndex = index - self.customEmojiStartIndex > 0 ? index - self.customEmojiStartIndex : 0;
        
        if (self.emojiSlider.alpha == 0) {
            
            if (self.isAnimationing) {
                return;
            }
            
            self.emojiSlider.alpha = 1;
            
            [self sliderAnimationWithState:YES];
            
        }
    } else {
        
        self.normalSelectedIndex = index;
        
        if (self.emojiSlider.alpha == 1) {
            
            [self sliderAnimationWithState:NO];
        }
    }
    
}

3. 按分類瀏覽過的表情生均,當(dāng)點擊底部分類按鈕時听想,跳轉(zhuǎn)到之前瀏覽過的頁面(僅支持鍵盤類型為包含自定義表情的類型)
處理方案:

聲明兩個屬性:customSelectedIndex(自定義表情用戶最后停留的index)和normalSelectedIndex(系統(tǒng)emoji表情用戶最后停留的index)。在- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView方法中記錄這兩個值马胧,當(dāng)點擊底部分類進行跳轉(zhuǎn)時汉买,遍歷整個collectionview的dataSource,依據(jù)用戶當(dāng)前所點擊的分類找到屬于該分類的第一個model所處的index佩脊,在index基礎(chǔ)上加上customSelectedIndex或者normalSelectedIndex蛙粘,使用- (void)collectionView的scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated方法進行跳轉(zhuǎn)

[weakSelf.dataSource enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            
   TMSEmoji *emoji = weakSelf.dataSource[idx];
   if (emoji.type == emojiType) { // 找到屬于該分類表情的第一頁所屬的index
                
       if (emojiType == TMSEmojiTypeCustom) {
          idx = idx + weakSelf.customSelectedIndex;
       } else {
          if (!weakSelf.hideCustomEmoji) {
             idx = weakSelf.normalSelectedIndex;
          }
       }
                
    [weakSelf.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:idx inSection:0] atScrollPosition:UICollectionViewScrollPositionNone animated:NO];
    weakSelf.emojiSlider.alpha = emojiType == TMSEmojiTypeCustom ? 1 : 0;
    [weakSelf.emojiSlider setValue:weakSelf.customSelectedIndex animated:YES];
     *stop = YES;
    }
}];

4. 當(dāng)調(diào)出表情鍵盤時垫卤,單擊輸入框重新調(diào)出系統(tǒng)鍵盤。
處理方案:

當(dāng)展示出表情鍵盤時出牧,對textView添加tap手勢穴肘,在tap手勢的action中重新調(diào)出系統(tǒng)鍵盤,并且移除tap手勢

if (sender.selected) { // 顯示表情鍵盤
    self.stickerView.sendActionBlock = ^(id emoji) {
        NSLog(@"發(fā)送emoji表情");
    };
    [self.stickerView setTextView:self.textView];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self.textView addGestureRecognizer:self.textViewTap];
     });
} else {
     self.textView.inputView = nil;
     [self.textView removeGestureRecognizer:self.textViewTap];
}

5. 表情的長按預(yù)覽功能
處理方案:

參考于PPStickerKeyboard的實現(xiàn)思路崔列,在TMSEmojItem上添加UILongPressGestureRecognizer梢褐,依據(jù)手勢recognizer.state的三種狀態(tài)(UIGestureRecognizerStateBegan, UIGestureRecognizerStateChanged, UIGestureRecognizerStateEnded)來控制標(biāo)簽預(yù)覽層的顯示與隱藏
自定義表情預(yù)覽層的邊框通過drawRect方法實現(xiàn),考慮到drawRect對性能的影響赵讯,也可以使用CAShapeLayer來替換drawRect方法盈咳。

Demo下載:https://github.com/TMMMMMS/TMSStickerView.git

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市边翼,隨后出現(xiàn)的幾起案子鱼响,更是在濱河造成了極大的恐慌,老刑警劉巖组底,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件丈积,死亡現(xiàn)場離奇詭異,居然都是意外死亡债鸡,警方通過查閱死者的電腦和手機江滨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來厌均,“玉大人唬滑,你說我怎么就攤上這事」妆祝” “怎么了晶密?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長模她。 經(jīng)常有香客問我稻艰,道長,這世上最難降的妖魔是什么侈净? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任尊勿,我火速辦了婚禮,結(jié)果婚禮上畜侦,老公的妹妹穿的比我還像新娘元扔。我一直安慰自己,他們只是感情好夏伊,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著吻氧,像睡著了一般溺忧。 火紅的嫁衣襯著肌膚如雪咏连。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天鲁森,我揣著相機與錄音祟滴,去河邊找鬼。 笑死歌溉,一個胖子當(dāng)著我的面吹牛垄懂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播痛垛,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼草慧,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了匙头?” 一聲冷哼從身側(cè)響起漫谷,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蹂析,沒想到半個月后舔示,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡电抚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年惕稻,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蝙叛。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡俺祠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出甥温,到底是詐尸還是另有隱情锻煌,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布姻蚓,位于F島的核電站宋梧,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏狰挡。R本人自食惡果不足惜捂龄,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望加叁。 院中可真熱鬧倦沧,春花似錦、人聲如沸它匕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽豫柬。三九已至告希,卻和暖如春扑浸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背燕偶。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工喝噪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人指么。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓酝惧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親伯诬。 傳聞我的和親對象是個殘疾皇子晚唇,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件姑廉、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,024評論 4 62
  • 我就是桥言。人際關(guān)系中的強者萌踱。做事的強者。哲學(xué)的強者号阿。我愛大神并鸵。
    老鄭_e744閱讀 235評論 1 0
  • 工作生活中园担,耳邊時常傳來一組混亂的對話。雙方都想讓對方明白自己的意思枯夜,可表述有問題弯汰,怎么解釋都說不通。最后可能還因...
    蘇菲的世界D閱讀 1,050評論 0 1
  • 坐動車到武漢湖雹,又飛了7個小時終于到達了夢想之地咏闪。本來早就想寫一篇游記,卻遲遲未下筆摔吏「肷可能是因為有太多想說的,卻不知...
    迷人小妖精的精閱讀 111評論 0 1
  • 01 「請教」 為理解而閱讀征讲,很容易遇到讀不懂的情況据某。這時你可能會選擇「請教」。但憑借外力幫助的閱讀诗箍,都不算是「真...
    何阿予閱讀 475評論 0 1