开源中国的emoji键盘类为EmojiPageVC
,继承自UIPageViewController
,为分页控制器。分页控制器的每个页面上的控制器类型为EmojiPanelVC
,继承自UIViewController
。 EmojiPanelVC
使用UICollectionView
来布局emoji。EmojiPanelVC
还有两个block回调:
void (^didSelectEmoji)(NSTextAttachment *textAttachment)
:表示的是点击某个emoji后的回调void (^deleteEmoji)()
:表示的是删除某个emoji的回调UICollectionView
的数据源是一个名为emoji.plist
的plist文件,只使用了其中的一部分。plist转为字典,key对应的的为名称字符串,value对应的为image的名称。如下:
点击UICollectionView
的某一项之后的调用的结果如下:
//选中一个emoji的图片
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger section = indexPath.section;
NSInteger row = indexPath.row;
if (section == 2 && row == 6) {
//block回调,删除
_deleteEmoji();
} else {
NSInteger emojiNum = _pageIndex * 20 + section * 7 + row + 1;
NSString *emojiImageName, *emojiStr;
if (emojiNum >= 106) {//emojiNum大于等于106的图片,图片名称类似为":finnadie:"
emojiStr = Utils.emojiDict[@(emojiNum).stringValue];
//去掉":"
emojiImageName = [emojiStr stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@":"]];
} else {//emojiNum小于106的图片,图片名称类似为"084"
emojiStr = [NSString stringWithFormat:@"[%ld]", emojiNum - 1];
emojiImageName = [NSString stringWithFormat:@"%03ld", emojiNum];
}
//创建Attachment
NSTextAttachment *textAttachment = [NSTextAttachment new];
textAttachment.image = [UIImage imageNamed:emojiImageName];
[textAttachment adjustY:-3];//调整位置
//给textAttachment关联一个对象
objc_setAssociatedObject(textAttachment, @"emoji", emojiStr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//block回调,参数为NSTextAttachment
_didSelectEmoji(textAttachment);
}
}
在EmojiPageVC
中处理回调。例如我们输入”we”后,再输入一个emoji。处理的过程如下:
_didSelectEmoji = ^(NSTextAttachment *textAttachment) {//选择了一个emoji
//通过NSTextAttachment创建emoji属性字符串
NSAttributedString *emojiAttributedString = [NSAttributedString attributedStringWithAttachment:textAttachment];
//通过textView的attributedText创建一个新的mutableAttributeString
NSMutableAttributedString *mutableAttributeString = [[NSMutableAttributedString alloc] initWithAttributedString:textView.attributedText];
//使用emojiAttributedString来替换
[mutableAttributeString replaceCharactersInRange:textView.selectedRange withAttributedString:emojiAttributedString];
//赋值给textView的attributedText
textView.attributedText = mutableAttributeString;
textView.textColor = [UIColor titleColor];
[textView insertText:@""];
textView.font = [UIFont systemFontOfSize:16];
};
删除emoji:
_deleteEmoji = ^ {//删除一个emoji
[textView deleteBackward];
};
发表一个”弹一弹”,在上传过程中如何处理emoji?
开源中国是这样处理的:
+ (NSString *)convertRichTextToRawText:(UITextView *)textView
{
NSMutableString *rawText = [[NSMutableString alloc] initWithString:textView.text];
//遍历其中的attachment
[textView.attributedText enumerateAttribute:NSAttachmentAttributeName
inRange:NSMakeRange(0, textView.attributedText.length)
options:NSAttributedStringEnumerationReverse
usingBlock:^(NSTextAttachment *attachment, NSRange range, BOOL *stop) {
if (!attachment) {return;}
//存在attachment,获取关联对象,即emojiStr,可能为[x]或者":xxxx:"
NSString *emojiStr = objc_getAssociatedObject(attachment, @"emoji");
//出入字符串
[rawText insertString:emojiStr atIndex:range.location];
}];
//匹配iOS系统的emoji
NSString *pattern = @"[\ue000-\uf8ff]|[\\x{1f300}-\\x{1f7ff}]|\\x{263A}\\x{FE0F}|☺";
NSError *error = nil;
NSRegularExpression *re = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:&error];
NSArray *resultsArray = [re matchesInString:textView.text options:0 range:NSMakeRange(0, textView.text.length)];
//emoji转为text的字典
NSBundle *bundle = [NSBundle mainBundle];
NSString *path = [bundle pathForResource:@"emojiToText" ofType:@"plist"];
NSDictionary *emojiToText = [[NSDictionary alloc] initWithContentsOfFile:path];
//把iOS系统的emoji替换为文字,例如":grinning::sweat_smile:"
for (NSTextCheckingResult *match in [resultsArray reverseObjectEnumerator]) {
NSString *emoji = [textView.text substringWithRange:match.range];
[rawText replaceCharactersInRange:match.range withString:emojiToText[emoji]];
}
return [rawText stringByReplacingOccurrencesOfString:@"\U0000fffc" withString:@""];
}
举个例子说明,我们输入以下的内容:
前两个emoji为iOS系统的emoji,后面连个是自定义emoji键盘的。
最看是rawText的内容为:
textView.attributedText遍历其中的NSAttachmentAttributeName之后,rawText的内容为:
需要注意的是,在emoji.plist文件中是这样定义的:
然后是替换,系统的emoji,之后rawText内容为:
发表”弹一弹”之后,在cell中显示动弹。网络获取的内容为:
aa:grinning::sweat_smile:[色][色]
这时需要把字符串替换为emoji
+ (NSAttributedString *)emojiStringFromRawString:(NSString *)rawString
{
//创建属性字符处
NSMutableAttributedString *emojiString = [[NSMutableAttributedString alloc] initWithString:rawString];
//emoji字典
NSDictionary *emoji = self.emojiDict;
//匹配emoji unicode编码[\u4e00-\u9fa5]匹配所有中文
NSString *pattern = @"\\[[a-zA-Z0-9\\u4e00-\\u9fa5]+\\]|:[a-zA-Z0-9\\u4e00-\\u9fa5_]+:";
NSError *error = nil;
NSRegularExpression *re = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:&error];
NSArray *resultsArray = [re matchesInString:rawString options:0 range:NSMakeRange(0, rawString.length)];
NSMutableArray *emojiArray = [NSMutableArray arrayWithCapacity:resultsArray.count];
for (NSTextCheckingResult *match in resultsArray) {
NSRange range = [match range];
NSString *emojiName = [rawString substringWithRange:range];
if ([emojiName hasPrefix:@"["] && emoji[emojiName]) {//自定义的emoji,带"["的
//创建attachment 添加图片
NSTextAttachment *textAttachment = [NSTextAttachment new];
textAttachment.image = [UIImage imageNamed:emoji[emojiName]];
//调整位置
[textAttachment adjustY:-3];
//创建attribute string
NSAttributedString *emojiAttributedString = [NSAttributedString attributedStringWithAttachment:textAttachment];
//创建字典,,添加到emojiArray中
[emojiArray addObject: @{@"image": emojiAttributedString, @"range": [NSValue valueWithRange:range]}];
} else if ([emojiName hasPrefix:@":"]) {//以":"开头的
if (emoji[emojiName]) {//emoji字典中存在的
[emojiArray addObject:@{@"text": emoji[emojiName], @"range": [NSValue valueWithRange:range]}];
} else {//emoji字典中不存在的
UIImage *emojiImage = [UIImage imageNamed:[emojiName stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@":"]]];
NSTextAttachment *textAttachment = [NSTextAttachment new];
textAttachment.image = emojiImage;
[textAttachment adjustY:-3];
NSAttributedString *emojiAttributedString = [NSAttributedString attributedStringWithAttachment:textAttachment];
[emojiArray addObject: @{@"image": emojiAttributedString, @"range": [NSValue valueWithRange:range]}];
}
}
}
//遍历emojiArray
for (NSInteger i = emojiArray.count -1; i >= 0; i--) {
NSRange range;
[emojiArray[i][@"range"] getValue:&range];//获取range
//替换range中的内容
if (emojiArray[i][@"image"]) {
[emojiString replaceCharactersInRange:range withAttributedString:emojiArray[i][@"image"]];
} else {
[emojiString replaceCharactersInRange:range withString:emojiArray[i][@"text"]];
}
}
return emojiString;
}
处理后的结果为:
Coding的emoji键盘使用的是AGEmojiKeyboardView
类,由AGEmojiKeyboard改变而来。支持普通的emoji,也支持大图Monkey和Monkey_Gif(自定义的)。如下:
点击一个emoji之后,调用的代理方法- (void)emojiKeyBoardView:(AGEmojiKeyboardView *)emojiKeyBoardView didUseEmoji:(NSString *)emoji
:
- (void)emojiKeyBoardView:(AGEmojiKeyboardView *)emojiKeyBoardView didUseEmoji:(NSString *)emoji {
NSRange selectedRange = self.tweetContentView.selectedRange;
NSString *emotion_monkey = [emoji emotionMonkeyName];
if (emotion_monkey) {//monkey类emoji表情
emotion_monkey = [NSString stringWithFormat:@" :%@: ", emotion_monkey];
self.tweetContentView.text = [self.tweetContentView.text stringByReplacingCharactersInRange:selectedRange withString:emotion_monkey];
self.tweetContentView.selectedRange = NSMakeRange(selectedRange.location +emotion_monkey.length, 0);
[self textViewDidChange:self.tweetContentView];
}else{//非monkey类emoji表情
self.tweetContentView.text = [self.tweetContentView.text stringByReplacingCharactersInRange:selectedRange withString:emoji];
self.tweetContentView.selectedRange = NSMakeRange(selectedRange.location +emoji.length, 0);
[self textViewDidChange:self.tweetContentView];
}
}
输入如下的emoji,,之后,textView的文本内容是:
上传emoji的时候,需要把emoji转为字符。Coding使用的是开源项目NSString-Emoji,在其基础上加了一些内容。转后的结果为:
:smiley: :哈哈: :奔月:
基本逻辑是遍历字符串,然后替换字符串:
[text enumerateSubstringsInRange:NSMakeRange(0, text.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
if (self.aliaseForEmojis[substring]) {
NSString *aliase = self.aliaseForEmojis[substring];
resultText = [resultText stringByReplacingOccurrencesOfString:substring withString:aliase];
}
}];