表情键盘 ---> 思路加实现

项目中有时候会用到表情键盘的使用,下面我就来写一下我所知道的简单实现.

第一步 ----------> 获取本地表情图片资源.

 表情键盘的展示,类似QQ,微信,就是一张张的图片(emoji表情除外,emoji表情为字符) [emoji表情](https://baike.baidu.com/item/emoji/8154456?fr=aladdin) 先从我们的本地拿到表情的图片,或者emoji表情,这个资源文件在所有的表情键盘中都有,就不解释了.
 我们之前说过,表情就是一张张的图片,我们在进行文字上传的时候,不可能上传一张张的图片,所以我们需要一个替代品,这里一般是和安卓后台约定好的,一般我们都设置为中括号里面写上文字即 [哈哈],[笑哭]等.为了方便展示使用.

资源结构:

  1. 加载emoticons.plist拿到每组表情的路径

emoticons.plist(字典) 存储了所有组表情的数据
|----packages(字典数组)
|-------id(存储了对应组表情对应的文件夹)

  1. 根据拿到的路径加载对应组表情的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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 168,494评论 26 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 10,717评论 4 57
  • ――珍惜机遇,蜕变成长 明天教研:舞动治疗。我很好奇,也好期待,决定拖着伤脚前去,即使不能舞动,现场...
    箫音声声阅读 161评论 0 1
  • 芸芸:“白痴,白痴,白痴?我想好了,我要学习写作。”芸芸像一只兴奋的小鹿,蹦蹦跳跳的冲进了白痴的工作间。 白痴:“...
    ad8d261a83e4阅读 139评论 2 0
  • 基本上午半天我有两小时是跑隔壁躺下纳凉的。 据说前天那个蒙古汉子打电话找我二领导了,说我没加他微信。 明明加了好么...
    木筱茜阅读 223评论 2 0