[iOS] 自定义表情包

一.前言

平常我们使用的表情包大多是emoji表情,而到了微信和qq上我们会见到更多更丰富的表情,那么这些表情是如何做的呢,下面就让我来带你们来揭晓答案。

注: 方法都来自互联网, 在此鸣谢走在前面铺路的前辈们.

二.准备阶段

准备一个表情包 就是一组表情如图1所示.

图1

我这里借用了一下腾讯的表情包 一共是50个表情

三.编写阶段, 这里只说明核心功能, UI简略说明.

1.效果图(you say a jb without a picture) 如图2所示.

图2

2.先画一个表情键盘

从效果图上可以看出来 下面的就是表情键盘(非常简陋! 我想吐) 咳咳... 如图3所示.

图3

这个表情键盘我是用collectionView画的(这个过程略), 使用过这个控件的人都知道, 我们还需要一个数据源来表示那些小表情, 接下来就是创建数据源的过程:

- (NSMutableArray *)faceArray {
    if (!_faceArray) {
        _faceArray = [NSMutableArray array];
        
        //创建一个标签数组
        NSArray *tagArr = @[@"[龇牙]", @"[吐舌]", @"[流汗]", @"[偷笑]", @"[再见]", @"[砸]", @"[擦汗]", @"[猪头]", @"[玫瑰]", @"[流泪]", @"[大哭]", @"[嘘]", @"[酷]", @"[抓狂]", @"[委屈]", @"[便便]", @"[地雷]", @"[菜刀]", @"[可爱]", @"[心心眼]", @"[害羞]", @"[帅气]", @"[吐]", @"[笑脸]", @"[生气]", @"[尴尬]", @"[惊吓]", @"[尴尬2]", @"[心]", @"[嘴唇]", @"[白眼]", @"[傲慢]", @"[难过]", @"[惊讶]", @"[疑问]", @"[睡觉]", @"[亲亲]", @"[憨笑]", @"[企鹅爱]", @"[衰]", @"[撇嘴]", @"[阴险]", @"[加油]", @"[发呆]", @"[睡着]", @"[抱抱]", @"[坏笑]", @"[飞吻]", @"[鄙视]", @"[晕]"];
        
        for (NSInteger i = 1; i < 51; i++) {
            //创建一个表情对象
            FaceAttachment *model = [[FaceAttachment alloc] init];
            //从第一张图片依次赋值
            model.imageName = [NSString stringWithFormat:@"%03ld", i];
            //刻上标签名 用途:上传服务器替换
            model.tagName = tagArr[i - 1];
            //将model装入数组
            [_faceArray addObject:model];
        }
    }
    return _faceArray;
}

下面是FaceAttachment
.h

#import <UIKit/UIKit.h>

@interface FaceAttachment : NSTextAttachment
@property(nonatomic, strong) NSString *imageName; /** 表情图片名 */
@property(nonatomic, strong) NSString *tagName; /** 标签名 */
@property(nonatomic, assign) NSRange range; /** 位置 */
@end

.m

#import "FaceAttachment.h"

@implementation FaceAttachment
- (UIImage *)image {
    return [UIImage imageNamed:_imageName];
}
@end

FaceAttachment的对象就是每个小表情, 这个model中有三个属性

  1. imageName 图片名 其实我们看到的表情就是一个个的小图片
  2. tagName 标签名 上传服务器时要将这些富文本中的表情 替换成普通的字符串标签才能上传
  3. range 记录表情的位置

3.控制键盘弹出逻辑

到这里有人会提出一个问题, 切换表情键盘后怎么让光标存在?
这个问题其实很简单, 只需要把表情键盘设置为textView.inputView就行了, 使用过程是先设置好inputView再弹出键盘, 不然inputView是不会自动切换的, 如果在键盘弹出状态下切换inputView也很简单, 方法就是先回收键盘 -> 切换inputView -> 再弹出即可 下面是表情与键盘的切换代码

- (IBAction)faceButton:(id)sender {
    
    if (!_keyBoardFlag) {
        
        [self.view endEditing:YES];
        self.textView.inputView = self.faceBoard;
        [self.textView becomeFirstResponder];
        [self.faceButton setImage:[UIImage imageNamed:@"键盘"] forState:UIControlStateNormal];
    }
    
    else {
        
        [self.view endEditing:YES];
        self.textView.inputView = nil;
        [self.textView becomeFirstResponder];
        [self.faceButton setImage:[UIImage imageNamed:@"表情"] forState:UIControlStateNormal];
    }
    
    _keyBoardFlag = !_keyBoardFlag;
}

4.插入表情的核心代码

其实原理很简单就是利用富文本进行一个表情的拼接 代码如下

+ (void)insertFaceToString:(FaceAttachment *)model textView:(UITextView *)textView {
    
    //创建一个附件
    FaceAttachment *faceAttachement = [[FaceAttachment alloc]init];
    //添加表情
    faceAttachement.imageName = model.imageName;
    //添加标签名
    faceAttachement.tagName = model.tagName;
    
    //设置表情大小
    faceAttachement.bounds = CGRectMake(0, 0, 18, 18);
    //记录光标位置
    NSInteger location = textView.selectedRange.location;
    //插入表情
    [textView.textStorage insertAttributedString:[NSAttributedString attributedStringWithAttachment:faceAttachement] atIndex:textView.selectedRange.location];
    //将光标位置向前移动一个单位
    textView.selectedRange = NSMakeRange(location + 1, 0);
}

其中 FaceAttachment 是继承于 NSTextAttachment 这个东西就相当于一个相框 用它装表情图片 然后拼接到文本上去, 为什么要继承 NSTextAttachment ? 因为我们要存放的东西在它原有基础上是不够的, 所以要继承 写上去三个属性

FaceAttachment的对象就是每个小表情, 这个model中有三个属性

  1. imageName 图片名 其实我们看到的表情就是一个个的小图片
  2. tagName 标签名 上传服务器时要将这些富文本中的表情 替换成普通的字符串标签才能上传
  3. range 记录表情的位置

5.上传服务器

通过上面的步骤 我想你们应该对表情是如何写在文本上有一个了解了 但是我要说的是 这样的表情是不能上传到服务器的, 所以我们要把它们转化成普通的纯文本字符才能去上传 下面的代码就是把这些富文本表情转化成纯文本字符.
转化前效果图:

图4

转化后文字:

请输入文字[龇牙][龇牙][龇牙]

下面是转化代码:

- (NSString *)toString {
    
    NSMutableAttributedString *attributeString = [[NSMutableAttributedString alloc]initWithAttributedString:self];
    
    __block NSUInteger index = 0;
    
    [self enumerateAttribute:NSAttachmentAttributeName inRange:NSMakeRange(0, self.length) options:0 usingBlock:^(id  _Nullable value, NSRange range, BOOL * _Nonnull stop) {
        
        //从富文本中遍历出 FaceAttachment 对象
        if (value && [value isKindOfClass:[FaceAttachment class]]) {
            FaceAttachment *faceAttachment = value;
            //替换对象为[表情]
            [attributeString replaceCharactersInRange:NSMakeRange(range.location + index, range.length) withString:faceAttachment.tagName];
            //替换后对位置作一下调整(因为替换前长度为1替换后有可能是4 [龇牙] 也有可能是3 [晕])
            index += faceAttachment.tagName.length - 1;
        }
    }];
    
    return attributeString.string;
}

上面的代码自己写写 很简单的.

6.服务器上获取的纯文本转化为带表情的富文本

通过上面的步骤 我们已经可以把表情和文字都转化成纯文本的形式上传的服务器上了, 接下来我们要做的就是把服务器上获取的这些字符串再转换回表情 其实中心思想就是用正则表达式过滤出"[表情]"这样的标签并记录他们的位置, 然后创建出 FaceAttachment 对象 对 "[表情]" 这样的标签来进行替换

- (NSAttributedString *)faceWithServerString:(NSString *)string {
    
    NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]initWithString:string];
    
    NSString *pattern = @"\\[[^\\[|^\\]]+\\]";
    NSError *error = nil;
    NSRegularExpression *regularExpression = [[NSRegularExpression alloc] initWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:&error];
    if (!regularExpression) {
        NSLog(@"错误信息 : %@", error);
    }
    
    //将匹配到的字符存入数组
    NSArray *resultArr = [regularExpression matchesInString:string options:0 range:NSMakeRange(0, string.length)];
    
    NSMutableArray *faceModelArr = [NSMutableArray array];
    
    for (NSTextCheckingResult *result in resultArr) {
        NSRange range = result.range;
        NSString *subString = [string substringWithRange:range];
        
        for (FaceAttachment *model in self.faceArray) {
            if ([subString isEqualToString:model.tagName]) {
                
                FaceAttachment *faceAttachment = [[FaceAttachment alloc]init];
                faceAttachment.imageName = model.imageName;
                faceAttachment.tagName = model.tagName;
                faceAttachment.range = range;
                faceAttachment.bounds = CGRectMake(0, 0, 18, 18);
                
                [faceModelArr addObject:faceAttachment];
            }
        }
    }
    
    faceModelArr = [NSMutableArray arrayWithArray:[[faceModelArr reverseObjectEnumerator] allObjects]];
    
    for (FaceAttachment *faceAttachment in faceModelArr) {
        NSAttributedString *faceAttributedString = [NSAttributedString attributedStringWithAttachment:faceAttachment];
        [attributedString replaceCharactersInRange:faceAttachment.range withAttributedString:faceAttributedString];
    }
    
    
    [attributedString addAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:14]} range:NSMakeRange(0, attributedString.length)];
    
    return attributedString;
}

四.Demo

下面是本人写的源码, 可以做为参考, 有不对的地方还请指教!
https://github.com/iwgo/CustomFace.git

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

推荐阅读更多精彩内容