iOS技巧之自定义Emoji键盘

思路分析

一般的思路就是先把本地的emoji图片加载到内存,然后把emoji图片按照特定排列方式显示到自定义的view中,并且能响应点击事件(即点击到自定义view上时能知道点击的是哪个emoji),同时自定义view能够进行翻页来支持大量的emoji。

加载emoji图片

笔者是采用Bundle的方式去管理项目中要用到的一百多个emoji表情,所以是要从Bundele中加载完所有的emoji图片(gif格式)。这里我是采用了YYImageDecoder去解码gif图片并且把它的第一帧图片放到表情图片数组中。为什么要使用YYImageDecoder去解码图片而不是直接用imageNamed:方法去加载请参考这篇文章: iOS 处理图片的一些小 Tip

         NSMutableArray *emotionArry = [NSMutableArray new];
          NSString *path = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:@"/myEmotion.bundle/"];
          for (int i = 1; i < 135; i++) {
              NSString *imgPath = [path stringByAppendingString:[NSString stringWithFormat:@"%d.gif",i]];
              NSData *data = [NSData dataWithContentsOfFile:imgPath];
              YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:0];
              UIImage *image = [decoder frameAtIndex:0 decodeForDisplay:YES].image;
              [emotionArry addObject:image];
          }

emoji图片显示到自定义view上

上一步已经把emoji加载了,这时候要考虑怎么把emoji图片显示到自定义view上。因为emoji键盘中的每一页表情明显是流水布局,不少人第一反应就是用UICollectionView去实现,其中每一页就是一个Cell,每个Cell里面就是放若干个背景是emoji的UIButton,这样只需要两个Cell就能完成复用了,同时点击emoji表情Button时候也能知道点击的那个Button。
其实用UICollectionView完成表情翻页的功能是正确的,但是错误的是Cell里面不应该用大量的UIButton去完成emoji的布局和点击功能,因为这样会消耗大量的资源去创建UIButton和处理其布局信息(如果用AutoLayout的话),翻页的时候会感受到明显的卡顿。
正确的方法应该把每一页的emoji画成一张图片,这样翻页的时候其实就是显示不同的图片。

            CGSize containerSize = CGSizeMake(ScreenWidth - 20, 92);  //表情键盘大小
            CGFloat horizonSpacing = (ScreenWidth - 20 - 9 * 24)/8;  //表情水平间隔
            CGFloat width = 24;
            CGFloat height = 24;
            NSMutableArray *containerArray = [NSMutableArray new];
            for (int i = 0; i < 5; i ++) {
                UIGraphicsBeginImageContext(CGSizeMake(ScreenWidth - 20, 92));//左右边距10 中间边距10
                CGContextRef context = UIGraphicsGetCurrentContext();
                [[UIColor whiteColor] setFill];
                CGContextFillRect(context, CGRectMake(0, 0, containerSize.width, containerSize.height));
                for (int j = 0; j < 27; j++) {
                    if (i == 4 && j == 26) {
                        continue;
                    }
                    UIImage *emotion = emotionArry[i * 27 + j];
                    CGRect emotionRect = CGRectMake((j % 9) * (width + horizonSpacing), floor(j/9) * (height + 10), width, height); // 计算出图片frame
                    [emotion drawInRect:emotionRect];   //把emoji绘制到画布上
                }
                UIImage *emotionContainerView = UIGraphicsGetImageFromCurrentImageContext();  //取出每一页的图片
                [containerArray addObject:emotionContainerView];
                UIGraphicsEndImageContext();
            }
            self.emotionsContainerViewArray = containerArray;

emoji的点击事件

emoji已经画成一张张的图片,但是但用户点击的时候如何知道他点击了哪个emoji?
因为UICollectionCell中只是简单的添加了一个UIImageView去显示图片,我们可以对UICollectionCell中添加tap手势,当触发手势时候,根据当前的Cell index和点击手势的x和y信息就可以算出用户点击的是哪个emoji。

- (void)touchEmotionsView:(UITapGestureRecognizer *)gesture {
  CGPoint point = [gesture locationInView:gesture.view];
  CGFloat horizonSpacing = (ScreenWidth - 20 - 9 * 24)/8;
  CGFloat width = 24;
  CGFloat height = 24;
  CGFloat verticalSpacing = 10;
  CGFloat x = point.x;
  CGFloat y = point.y;
  int indexX = floor(x/(horizonSpacing + width));
  int indexY = floor(y/(verticalSpacing + height));
  int index = (int)_indexPath.row * 27 + indexY * 9 + indexX;
  if (index > 133) {
      return;
  } else {
      NSString *emotion = [NSString stringWithFormat:@"[%d.gif]",index];
      if ([self.parent.delegate respondsToSelector:@selector(selectedEmotion:)]) {
          [self.parent.delegate selectedEmotion:emotion];
      }
  }
}

缓存绘制好的emoji图片

其实功能已经实现的差不多了,一般来说这个自定义emoji键盘都是实现成单例的,需要用的时候把这个emoji键盘view赋值给UITextView/UITextField的inputview就能把它显示到键盘的位置中去。
但是没必要每次重新启动程序时需要用到这个单例都要在初始化单例时候去重新画emoji图片,我们可以把画好的emoji图片缓存在本地,后面每次初始化时候就可以直接加载缓存的图片而不用重新绘制。

(void)initEmotionsContainer {
  dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
      NSFileManager *fileManager = [[NSFileManager alloc] init];
      NSString *pathDocuments = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
      NSString *folderPath = [pathDocuments stringByAppendingPathComponent:@"emotion"];
      // 判断文件夹是否存在,如果不存在,则创建
      if (![[NSFileManager defaultManager] fileExistsAtPath:folderPath]) {
          JZLog(@"创建文件夹成功 %@", folderPath);
          [fileManager createDirectoryAtPath:folderPath withIntermediateDirectories:YES attributes:nil error:nil];
                /****
                  绘制emoji图片
                ***/
              UIImage *emotionContainerView = UIGraphicsGetImageFromCurrentImageContext();
              [containerArray addObject:emotionContainerView];
              NSData *data = [emotionContainerView yy_imageDataRepresentation];
              NSString *dataPath = [folderPath stringByAppendingPathComponent:[NSString stringWithFormat:@"emotion%d", i]];
              [data writeToFile:dataPath atomically:YES];
          }
          self.emotionsContainerViewArray = containerArray;
          dispatch_async(dispatch_get_main_queue(), ^{
              [self.collectionView reloadData];
          });
      } else {
          JZLog(@"从磁盘加载表情");
          NSMutableArray *containerArray = [NSMutableArray new];
          for (int i = 0; i < 5; i++) {
              NSString *dataPath = [folderPath stringByAppendingPathComponent:[NSString stringWithFormat:@"emotion%d", i]];
              NSData *data = [NSData dataWithContentsOfFile:dataPath];
              UIImage *emotionContainer = [UIImage imageWithData:data];
              [containerArray addObject:emotionContainer];
          }
          self.emotionsContainerViewArray = containerArray;
          dispatch_async(dispatch_get_main_queue(), ^{
              [self.collectionView reloadData];
          });
      }
  });
}

结尾

自定义Emoji键盘的就完成的差不多,其主要思路同时通过绘制控件而不是添加控件去减少对象的创建,并且加载缓存数据实现一次绘制多次使用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,397评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • 首先给大家推荐一个Mac上的壁纸软件Irvue,这是可以自动更换桌面壁纸的软件,而其不同于其他软件的是,它的图片库...
    SSBun阅读 1,321评论 0 0
  • 在家等老妈的饭吃,电视正在播西游记,三打白骨精。小时候可爱看了。 一打,师傅赶大师兄走,大师兄说:师傅,我尚有一事...
    恋念依旧阅读 914评论 0 1