项目中有时候会用到表情键盘的使用,下面我就来写一下我所知道的简单实现.
第一步 ----------> 获取本地表情图片资源.
表情键盘的展示,类似QQ,微信,就是一张张的图片(emoji表情除外,emoji表情为字符) [emoji表情](https://baike.baidu.com/item/emoji/8154456?fr=aladdin) 先从我们的本地拿到表情的图片,或者emoji表情,这个资源文件在所有的表情键盘中都有,就不解释了.
我们之前说过,表情就是一张张的图片,我们在进行文字上传的时候,不可能上传一张张的图片,所以我们需要一个替代品,这里一般是和安卓后台约定好的,一般我们都设置为中括号里面写上文字即 [哈哈],[笑哭]等.为了方便展示使用.
资源结构:
- 加载emoticons.plist拿到每组表情的路径
emoticons.plist(字典) 存储了所有组表情的数据
|----packages(字典数组)
|-------id(存储了对应组表情对应的文件夹)
-
根据拿到的路径加载对应组表情的info.plist
info.plist(字典)
|----id(当前组表情文件夹的名称)
|----group_name_cn(组的名称)
|----emoticons(字典数组, 里面存储了所有表情)
|----chs(表情对应的文字)
|----png(表情对应的图片)
|----code(emoji表情对应的十六进制字符串)
1.创建一个资源管理类,用来管理我们的表情图片资源EmoticonPackage,在这个类中,我们需要进行的处理有获取所有组的表情,并用对象进行保存,这个比较简单,就不做过多叙述了.
2.我们需要一个方法,可以传入sring即普通文字,返回attributeString 富文本,这个方法是为了之后,我们从服务器获取到 [大笑] 这样的文字后可以用本地的图片进行替换,方便快捷.
/// 根据传入的字符串, 返回属性字符串
class func emoticonString(_ str: String) -> NSAttributedString? {
// 生成完整的属性字符串
let strM = NSMutableAttributedString(string: str)
do{
// 1.创建规则
let pattern = "\\[.*?\\]"
// 2.创建正则表达式对象
let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options.caseInsensitive)
// 3.开始匹配
let res = regex.matches(in: str, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, str.characters.count))
// 4取出结果
var count = res.count
while count > 0
{
// 0.从后面开始取
let checkingRes = res[--count]
// 1.拿到匹配到的表情字符串
let tempStr = (str as NSString).substring(with: checkingRes.range)
// 2.根据表情字符串查找对应的表情模型
if let emoticon = emoticonWithStr(tempStr)
{
print(emoticon.chs)
// 3.根据表情模型生成属性字符串
let attrStr = EmoticonTextAttachment.imageText(emoticon, font: UIFont.systemFont(ofSize: 18))
// 4.添加属性字符串
strM.replaceCharacters(in: checkingRes.range, with: attrStr)
}
}
// 拿到替换之后的属性字符串
return strM
}catch
{
print(error)
return nil
}
}
/**
根据表情文字找到对应的表情模型
:param: str 表情文字
:returns: 表情模型
*/
class func emoticonWithStr(_ str: String) -> Emoticon?
{
var emoticon: Emoticon?
for package in EmoticonPackage.packageList
{
emoticon = package.emoticons?.filter({ (e) -> Bool in
return e.chs == str
}).last
if emoticon != nil{
break
}
}
return emoticon
}
我们这此要对表情模型有一个小小的判断,是不是emoji表情还是图片表情.
在模型属性中有一个属性对此进行判断.见第四步
第二步 --------> 创建表情键盘inputView
我们都知道,如果要切换键盘,那么我们就要改变textView或者textField的inputView 属性 .如下所示:
// 如果是系统自带的键盘, 那么inputView = nil
// 如果不是系统自带的键盘, 那么inputView != nil
// 1.关闭键盘
textView.resignFirstResponder()
// 2.设置inputView
textView.inputView = (textView.inputView == nil) ? view(这个view即为我们的表情键盘view) : nil
// 3.从新召唤出键盘
textView.becomeFirstResponder()
为了之后的使用方便,体现封装思想,我们要创建第二个 类 EmoticonViewController ,我们之后只需要在控制器中添加一个子控制器EmoticonViewController,把它的view当做键盘的inputView 即可.在这个类中,我们需要创建键盘view ,这里我们使用collectionView,进行类似横向瀑布流的展示,但是,需要注意的一点是,
collectionView流水布局flow,item等宽,间距相等,需要设置一个属性,即需要设置contentInset左右间距和margin相等,否则会有问题(黑线)
第三步 --------------> 将表情 模型 转为 富文本
我们之前说过,除了emoji表情,其他都是图片,我们在点击表情的时候,需要的就是想textView或textField中插入图片了,这个时候怎么处理呢?这个时候我们就要用到textView的一个属性了,attributeString,在Swift中是attributedText. 没错,就是富文本文字.
熟悉富文本的开发者都知道,我们在富文本中,是可以插入图片的,即NSTextAttachment.这里简单介绍一下如何使用,很简单,不做过多解释
//设置Attachment
NSTextAttachment *attachment = [[NSTextAttachment alloc] init];
//使用一张图片作为Attachment数据
attachment.image = [UIImage imageNamed:@"test"];
attachment.bounds = CGRectMake(-4, 0, 20, 10);
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:@"这是一串字"];
[attributedString appendAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]];
label.attributedText = attributedString;
这里我们创建我们的第三个类 EmoticonTextAttachment 继承自NSTextAttachment ,这里的目的是为了:方便我们进行图片的展示,当我们点击cell的时候,我们拿到了表情的模型,但是,我们需要的是一个富文本来进行展示,所有,我们构建一个类方法,我们提供一个表情模型,返回给我们一个图片的富文本,我们直接可以拿来使用,方便,快捷,高效.废话不多说,上代码
class EmoticonTextAttachment: NSTextAttachment {
// 保存对应表情的文字
var chs: String?
/// 根据表情模型, 创建表情字符串
class func imageText(_ emoticon: Emoticon, font: UIFont) -> NSAttributedString{
// 1.创建附件
let attachment = EmoticonTextAttachment()
attachment.chs = emoticon.chs
attachment.image = UIImage(contentsOfFile: emoticon.imagePath!)
// 设置了附件的大小
let s = font.lineHeight
attachment.bounds = CGRect(x: 0, y: -4, width: s, height: s)
// 2. 根据附件创建属性字符串
return NSAttributedString(attachment: attachment)
}
}
第四步 --------------> 表情富文本的插入 与 需要上传到服务器的文字
经过以上的操作,我们拿到了表情的富文本,这里我们需要注意一点,那就是光标的位置,对我们插入表情富文本的影响,光标的位置不同,我们插入的位置也不能相同,这个时候我们需要怎么进行处理呢???
这里我们需要介绍另一个知识点,selectedRange与selectedTextRange类型不同,我们也要做不同的处理
小demo -> textView或者label,设置点击位置高亮原理
//设置了textView的selectedRange为NSRange类型,selectedTextRange,为UITextRange类型,textView的范围判断是这样判断的
// 给定指定的range, 返回range对应的字符串的rect
// 返回数组的原因是因为文字可能换行
self.tv.selectedRange = spec.range;
NSArray *rects = [self.tv selectionRectsForRange:self.tv.selectedTextRange];
self.tv.selectedRange = NSMakeRange(0, 0);
for (UITextSelectionRect *selectionRect in rects) {
CGRect rect = selectionRect.rect;
if (rect.size.width == 0 || rect.size.height == 0) continue;
if (CGRectContainsPoint(rect, point)) {
return YES;
}
else
{
return NO;
}
}
这里对点击的表情是emoji表情还是图片表情做了判断
1.如果是emoji表情
2.如果是图片表情
上代码
func insertEmoticon(_ emoticon: Emoticon)
{
// 0.处理删除按钮
if emoticon.isRemoveButton
{
deleteBackward()
}
// 1.判断当前点击的是否是emoji表情
if emoticon.emojiStr != nil{
self.replace(self.selectedTextRange!, withText: emoticon.emojiStr!)
}
// 2.判断当前点击的是否是表情图片
if emoticon.png != nil{
// print("font = \(font)")
// 1.创建表情字符串
let imageText = EmoticonTextAttachment.imageText(emoticon, font: font ?? UIFont.systemFont(ofSize: 17))
// 3.拿到当前所有的内容
let strM = NSMutableAttributedString(attributedString: self.attributedText)
// 4.插入表情到当前光标所在的位置
let range = self.selectedRange
strM.replaceCharacters(in: range, with: imageText)
// 属性字符串有自己默认的尺寸
strM.addAttribute(NSFontAttributeName, value: font! , range: NSMakeRange(range.location, 1))
// 5.将替换后的字符串赋值给UITextView
self.attributedText = strM
// 恢复光标所在的位置
// 两个参数: 第一个是指定光标所在的位置, 第二个参数是选中文本的个数
self.selectedRange = NSMakeRange(range.location + 1, 0)
// 6.自己主动促发textViewDidChange方法
delegate?.textViewDidChange!(self)
}
}
这时,我们还需要一个上传时,要把我们的富文本文字转成 普通文字,这是的富文本文字为
/**
获取需要发送给服务器的字符串
*/
func emoticonAttributedText() -> String
{
var strM = String()
// 后去需要发送给服务器的数据
attributedText.enumerateAttributes( in: NSMakeRange(0, attributedText.length), options: NSAttributedString.EnumerationOptions(rawValue: 0)) { (objc, range, _) -> Void in
if objc["NSAttachment"] != nil
{
// 图片
let attachment = objc["NSAttachment"] as! EmoticonTextAttachment
strM += attachment.chs!
}else
{
// 文字
strM += (self.text as NSString).substring(with: range)
}
}
return strM
}
大功告成 ,项目思路来源于小码哥-李南江老师的讲解.记录一下.
我没有写demo,在GitHub发现了一个比较好的表情键盘 ,如果有需要,可以去看下
- https://github.com/itheima-developer/HMEmoticon