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

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

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

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

推荐阅读更多精彩内容

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