iOS emoji应用(一)

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

 

最近公司一个项目需要用到emoji表情,在使用的过程中出现了一些问题。一同事问我说服务器传输过来的是emoji表情在我们这边显示成了编码\ud83d\ude04。我说是转义问题,但是他折腾了半天硬是没搞懂,我叫他把代码发过来,然而服务器却在内网,无法链接到服务器。于是乎我就写了这篇文章。

emoji的起源

表情符号,来自日语词汇“絵文字”(假名为“えもじ”,读音即emoji),是一套起源于日本的12x12像素表情符号,由栗田穣崇(Shigetaka Kurit)创作,最早在日本网络及手机用户中流行。 自苹果公司发布的iOS 5输入法中加入了emoji后,这种表情符号开始席卷全球,目前emoji已被大多数现代计算机系统所兼容的Unicode编码采纳,普遍应用于各种手机短信和社交网络中。----来自互动百科

上面说到“emoji已被大多数现代计算机系统所兼容的Unicode编码采纳,普遍应用于各种手机短信和社交网络中。”,换句话来说就是还有少数的计算机系统和手机的emoji表情采用的不是Unicode编码。查阅资料得知在最初日本的三大电信公司NTT DoCoMo、au/KDDI和Softbank,实现的方式是以下几种

  • NTT DoCoMo的i-mode系统电话系统中,绘文字的尺寸是12x12 像素,在传送时,一个图形有2个字节。Unicode编码为E63E到E757
  • au/KDDI的emoji体系则是通过特别的IMG标签实现
  • Softbank的emoji是用SI/SO escape sequence所编码的。

还有一点就是,在不同的操作系统上emoji表情也可能会不一样,例如?

编码 Apple Google Microsoft Samsung LG
U+1F600

iOS emoji应用(一)_第1张图片

iOS emoji应用(一)_第2张图片

1240

iOS emoji应用(一)_第3张图片

iOS emoji应用(一)_第4张图片

编码 HTC Twitter Facebook Mozilla Emoji One
U+1F600

iOS emoji应用(一)_第5张图片

iOS emoji应用(一)_第6张图片

1240

iOS emoji应用(一)_第7张图片

iOS emoji应用(一)_第8张图片

编码 emojidex        
U+1F600

iOS emoji应用(一)_第9张图片

       

emoji的Unicode

通常查看的文档和资料中emoji的unicode编码都是Unicode编码呈现的,例如?的编码是:U+1F604

?的编码表:

表情 Unicode UTF-16 UTF8 SB Unicode
? U+1F604 0xD83D 0xDE04 0xF0 0x9F 0x98 0x84 E415

更多的编码请查询:http://punchdrunker.github.io/iOSEmoji/table_html/index.html

iOS开发中的emoji

在iOS系统中emoji是以编码的形式存在(在系统中应该有个编码值和图片的关系表),在输入emoji编码的时候系统会根据编码找到对应的图片进行显示。在iOS中的所有能输入文字的原生控件都支持emoji表情的显示,也就是说如果你使用的是Unicode可以在不做任何转换的前提下显示出来,当然前提是你的emoji编码是正确的。
在开发中输入emoji: control + command + Space 就会弹出emoji窗口

iOS emoji应用(一)_第10张图片

macEmoji.png


如果需要输入编码的话如下:

     //在这里以?表情为例,?的Unicode编码为U+1F604,UTF-16编码为:\ud83d\ude04
    NSString * emojiUnicode = @"\U0001F604";
    NSLog(@"emojiUnicode:%@",emojiUnicode);
    //如果直接输入\ud83d\ude04会报错,加了转义后不会报错,但是会输出字符串\ud83d\ude04,而不是?
    NSString * emojiUTF16 = @"\\ud83d\\ude04";
    NSLog(@"emojiUTF16:%@",emojiUTF16);
    //转换
    emojiUTF16 = [NSString stringWithCString:[emojiUTF16 cStringUsingEncoding:NSUTF8StringEncoding] encoding:NSNonLossyASCIIStringEncoding];
    NSLog(@"emojiUnicode2:%@",emojiUTF16);

运行输出:

    emojiUnicode:?
    emojiUnicode1:\ud83d\ude04
    emojiUnicode2:?

回到我同事那个问题,服务器传输过来的值如果是\ud83d\ude04那么在用label或者其他系统控件显示时出现的应该是?表情,但是他说出现的是\ud83d\ude04,所以服务器传输过来的值应该是\\ud83d\\ude04,那问题来了,难道每次都需要对字符串进行转换操作么?显然不应该,让服务端直接返回\ud83d\ude04编码是比较好的选择。

iOS的emoji键盘开发

iOS本身是有自带键盘的,但是为了让用户有更好的体验往往会添加个emoji键盘。emoji键盘的样式很多种,在这里我们就以QQ的emoji键盘为例如图:

iOS emoji应用(一)_第11张图片

keyboard.png


从上图来看,去掉那些跟emoji关系不大的成分,就只剩下中间那块是需要我们做的,先分析一个键盘需要的功能:

  • 1.显示
  • 2.删除
  • 3.输出

观察上图并结合功能发现UICollectionView满足我们的需求,当然UIScrollView也能实现,但是为了方便在这里选用的是UICollectionView。
我准备了一个emoji的plist文件,用来导入emoji排序和分组导入工程

iOS emoji应用(一)_第12张图片

emojisPlist.png


然后处理成[[Srting]]格式。(在这里使用的是Swift语言,因为最近闲着无聊用Swift仿QQ,所以直接用swift写了)
数据准备好了后就直接开始码界面。代码如下:

HHEmojiKeyboard.swift

let HHEmojiKeyboard_Column:CGFloat = 7

// MARK: - HHEmojiKeyboardDelegate 协议
protocol HHEmojiKeyboardDelegate:NSObjectProtocol {
    /**
     点击选择表情

     - parameter emojiKeyboard: emoji键盘
     - parameter emoji:         emoji表情
     */
    func emojiKeyboard(emojiKeyboard:HHEmojiKeyboard,didSelectEmoji emoji:String)

    /**
     点击删除按钮

     - parameter emojiKeyboard: emoji键盘
     */
    func emojiKeyboardDidSelectDelete(emojiKeyboard:HHEmojiKeyboard)

    /**
     翻页

     - parameter emojiKeyboard: emoji键盘
     - parameter pageIndex:     页面的下标从0开始
     */
    func emojiKeyboard(emojiKeyboard:HHEmojiKeyboard,scrollDidTo pageIndex:Int)
}

class HHEmojiKeyboard: UICollectionView,UICollectionViewDelegate,UICollectionViewDataSource,UIScrollViewDelegate {
     /// 数据源
    var dataArr:[[String]]!
     /// 是否显示删除按钮
    var delete:Bool = false
     /// 协议
    weak var emojiKeyboardDelegate:HHEmojiKeyboardDelegate?
     /// 每页emoji表情的总数,这里加上了删除按钮
    var pageEmojiCount:Int!{
        get{
            /// 行数
            let line = self.frame.size.height / (self.frame.width / HHEmojiKeyboard_Column)
            /// 一页的表情数
            let pageEmojiCount = Int(line) * Int(HHEmojiKeyboard_Column)
            return pageEmojiCount
        }
    }

    /**
     构造方法

     - parameter frame:  位置大小
     - parameter layout: 布局
     - parameter arr:    [String]数据,需要处理
     - parameter delete: 是否显示删除按钮

     - returns: HHEmojiKeyboard实例
     */
    init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout ,stringArr arr:[String]!,isShowDelete delete:Bool) {
        super.init(frame: frame, collectionViewLayout: layout)
        self.registerClass(HHEmojiKeyboardCell.self, forCellWithReuseIdentifier: "HHEmojiKeyboardCell")
        self.registerClass(HHImageKeyboardCell.self, forCellWithReuseIdentifier: "HHImageKeyboardCell")
        self.pagingEnabled = true
        self.dataSource = self
        self.delegate = self
        self.showsHorizontalScrollIndicator = false
        self.delete = delete
        self.grouping(arr)
    }

    /**
     构造方法

     - parameter frame:  位置大小
     - parameter layout: 布局
     - parameter arr:    分组数据
     - parameter delete: 是否显示删除按钮

     - returns: HHEmojiKeyboard实例
     */
    init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout ,groupingArr arr:[[String]]!,isShowDelete delete:Bool){
        super.init(frame: frame, collectionViewLayout: layout)
        self.registerClass(HHEmojiKeyboardCell.self, forCellWithReuseIdentifier: "HHEmojiKeyboardCell")
        self.registerClass(HHImageKeyboardCell.self, forCellWithReuseIdentifier: "HHImageKeyboardCell")
        self.pagingEnabled = true
        self.dataSource = self
        self.delegate = self
        self.showsHorizontalScrollIndicator = false
        self.delete = delete
        self.dataArr = arr
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // MARK: - UICollectionViewDataSource
    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int{
        return self.pageEmojiCount
    }

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell{
        if indexPath.row == self.pageEmojiCount - 1 && self.delete{
            let cell = collectionView.dequeueReusableCellWithReuseIdentifier("HHImageKeyboardCell", forIndexPath: indexPath) as!  HHImageKeyboardCell
            cell.imgView.image = UIImage(named: "aio_face_delete")
            return cell
        }else{
            let cell = collectionView.dequeueReusableCellWithReuseIdentifier("HHEmojiKeyboardCell", forIndexPath: indexPath) as!  HHEmojiKeyboardCell
            if self.dataArr[indexPath.section].count > indexPath.row {
                cell.emojiLabel.text = self.dataArr[indexPath.section][indexPath.row]
            }else{
                cell.emojiLabel.text = ""
            }
            return cell
        }
    }

    func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int{
        return self.dataArr.count
    }

    // MARK: - UICollectionViewDelegate
    func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath){
        if let delegate = self.emojiKeyboardDelegate {
            if let cell = collectionView.cellForItemAtIndexPath(indexPath){
                if cell.isKindOfClass(HHEmojiKeyboardCell){
                    if let content = (cell as! HHEmojiKeyboardCell).emojiLabel.text {
                        if content.characters.count > 0{
                            delegate.emojiKeyboard(self, didSelectEmoji: content)
                        }
                    }
                }else if cell.isKindOfClass(HHImageKeyboardCell){
                    delegate.emojiKeyboardDidSelectDelete(self)
                }
            }
        }
    }

    // MARK: - UICollectionViewDelegateFlowLayout
    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize{
        return CGSizeMake(self.frame.size.width / HHEmojiKeyboard_Column, self.frame.size.width / HHEmojiKeyboard_Column)
    }

    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets{
        return UIEdgeInsetsZero
    }

    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat{
        return 0
    }

    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat{
        return 0
    }

    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize{
        return CGSizeZero
    }

    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize{
        return CGSizeZero
    }

    // MARK: - UIScrollViewDelegate
    func scrollViewDidScroll(scrollView: UIScrollView) {
        var page = Int(scrollView.contentOffset.x / scrollView.frame.size.width)
        if Int(scrollView.contentOffset.x)%Int(scrollView.frame.size.width) > 0 {
            page += 1
        }
        if let delegate = self.emojiKeyboardDelegate {
            delegate.emojiKeyboard(self, scrollDidTo: page)
        }
    }
    // MARK: - 分组
    func grouping(arr:[String]!){
        var pageEmojiCount = self.pageEmojiCount
        if self.delete {
            pageEmojiCount = pageEmojiCount - 1
        }

        var pageNumber:Int = arr.count / pageEmojiCount
        if arr.count%pageEmojiCount > 0 {
            pageNumber += 1
        }
        self.dataArr = []
        var emojis:[String] = []

        for i in 0..= arr.count {
                    break
                }
                emojis.append(arr[i*pageEmojiCount+j])
            }
            self.dataArr.append(emojis)
        }
    }
}

HHEmojiKeyboardCell.swift

class HHEmojiKeyboardCell: UICollectionViewCell {
    /// emoji表情标签
    var emojiLabel:UILabel!

    // MARK: - 父类方法
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.initUI()
    }

    required init?(coder aDecoder: NSCoder) {
       super.init(coder: aDecoder)
        self.initUI()
    }


    override func updateConstraints() {
        super.updateConstraints()
        let size = self.frame.size
        self.emojiLabel.font = UIFont.systemFontOfSize(size.width/2)
    }

    // MARK: - 初始化
    /**
     初始化UI
     */
    func initUI(){
        self.emojiLabel = UILabel()
        self.emojiLabel.textAlignment = .Center
        self.emojiLabel.translatesAutoresizingMaskIntoConstraints = false
        self.addSubview(self.emojiLabel)

        let vflH = "H:|-0-[emojiLabel]-0-|"
        let vflV = "V:|-0-[emojiLabel]-0-|"

        let hLayout =  NSLayoutConstraint.constraintsWithVisualFormat(vflH, options: NSLayoutFormatOptions.DirectionLeadingToTrailing, metrics: nil, views: ["emojiLabel" : self.emojiLabel])
        let vLayout =  NSLayoutConstraint.constraintsWithVisualFormat(vflV, options: NSLayoutFormatOptions.DirectionLeadingToTrailing, metrics: nil, views: ["emojiLabel" : self.emojiLabel])
        self.addConstraints(hLayout)
        self.addConstraints(vLayout)
    }

}

HHImageKeyboardCell.swift

class HHImageKeyboardCell: UICollectionViewCell {
    /// emoji表情标签
    var imgView:UIImageView!
    // MARK: - 父类方法
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.initUI()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.initUI()
    }

    // MARK: - 初始化
    /**
     初始化UI
     */
    func initUI(){
        self.imgView = UIImageView()
        self.imgView.contentMode =  UIViewContentMode.Center
        self.imgView.translatesAutoresizingMaskIntoConstraints = false
        self.addSubview(self.imgView)

        let vflH = "H:|-0-[imgView]-0-|"
        let vflV = "V:|-0-[imgView]-0-|"

        let hLayout =  NSLayoutConstraint.constraintsWithVisualFormat(vflH, options: NSLayoutFormatOptions.DirectionLeadingToTrailing, metrics: nil, views: ["imgView" : self.imgView])
        let vLayout =  NSLayoutConstraint.constraintsWithVisualFormat(vflV, options: NSLayoutFormatOptions.DirectionLeadingToTrailing, metrics: nil, views: ["imgView" : self.imgView])
        self.addConstraints(hLayout)
        self.addConstraints(vLayout)
    }

}

HHEmojiManage.swift

import Foundation


class HHEmojiManage:NSObject {

    class func getEmojiAll() -> NSDictionary{
        if let path = NSBundle.mainBundle().pathForResource("EmojisList", ofType: "plist"){
            if let dic = NSDictionary(contentsOfFile: path as String){
                return dic
            }
        }
       return NSDictionary()
    }

}

ViewController.swift

class ViewController: UIViewController ,HHEmojiKeyboardDelegate{
    var keyboard:HHEmojiKeyboard!
    var textView:UITextView!
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        let dic = HHEmojiManage.getEmojiAll()
        var emojiArr:[String] = []
        for arr in dic.allValues {
            emojiArr = arr as! [String]
            break
        }
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .Horizontal
        layout.sectionInset = UIEdgeInsetsZero
        let width:CGFloat = self.view.frame.size.width
        let frame = CGRectMake(0, self.view.frame.size.height - width*3/7, width, width*3/7)
        self.keyboard = HHEmojiKeyboard(frame: frame, collectionViewLayout: layout, stringArr: emojiArr, isShowDelete: true)
        self.keyboard.emojiKeyboardDelegate = self
        self.keyboard.backgroundColor = UIColor.whiteColor()
        self.view.addSubview(self.keyboard)
        let textFiledFrame = CGRectMake(0, CGRectGetMinY(self.keyboard.frame) - 30, width, 30)
        self.textView = UITextView(frame: textFiledFrame)
        self.textView.backgroundColor = UIColor.grayColor()
        self.view.addSubview(self.textView)
   }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // MARK: - HHEmojiKeyboardDelegate
    func emojiKeyboard(emojiKeyboard:HHEmojiKeyboard,didSelectEmoji emoji:String){
        self.textView.text?.appendContentsOf(emoji)
    }

    func emojiKeyboardDidSelectDelete(emojiKeyboard:HHEmojiKeyboard){
        self.textView.deleteBackward()
    }

    func emojiKeyboard(emojiKeyboard: HHEmojiKeyboard, scrollDidTo pageIndex: Int) {
        print(pageIndex)
    } 
 }

运行效果如下:

iOS emoji应用(一)_第13张图片

run1.png

看上去好像没什么问题,但是当我翻到最后一页的时候发现效果不对。如图:

iOS emoji应用(一)_第14张图片

run2.png

所以问题来了,如何得到我们想要的效果。将emoji表情和删除图标换成cell的下标,得到下图:

iOS emoji应用(一)_第15张图片

emoji表情和删除图标换成cell的下标


发现UICollectionView竟然是竖着排的。我能想到的解决的方法有两种。

  • 1.让UICollectionView横着排
  • 2.挪动数据的位置,无数据的位置由长度为0的字符串补上

    第一种的实现方法也简单,重写UICollectionViewFlowLayout的func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]?方法

代码如下:

HHCollectionViewFlowLayout.swift

import UIKit

class HHCollectionViewFlowLayout: UICollectionViewFlowLayout {
    override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {

        if let attributes = super.layoutAttributesForElementsInRect(rect){
            let attArr = NSArray(array: attributes, copyItems: true) as! [UICollectionViewLayoutAttributes]
            for att in attArr {
                let size = att.size
                let x = CGFloat(att.indexPath.row%Int(HHEmojiKeyboard_Column))*size.width+CGFloat(att.indexPath.section) * self.collectionView!.frame.size.width
                let y = CGFloat(att.indexPath.row/Int(HHEmojiKeyboard_Column))*size.height
                att.frame.origin.x = x
                att.frame.origin.y = y
            }
            return attArr
        }

        return nil
    }
}

将ViewController中的UICollectionViewFlowLayout换成HHCollectionViewFlowLayout
运行效果如下:

iOS emoji应用(一)_第16张图片

run3.png

问题解决。

第二种的实现方法,则是挪动数据的位置,将数据看成矩阵,需要做的就是
将矩阵1

iOS emoji应用(一)_第17张图片

Matrix.png

转换成矩阵2

iOS emoji应用(一)_第18张图片

Matrix2.png

观察发现规律:

原来位置->目标位置 原来位置->目标位置 原来位置->目标位置
0 -> 0 7 -> 1 14 -> 2
1 -> 3 8 -> 4 15 -> 5
2 -> 6 9 -> 7 16 -> 8
3 -> 9 10 -> 10 17 -> 11
4 -> 12 11 -> 13 18 -> 14
5 -> 15 12 -> 16 19 -> 17
6 ->18 13 -> 19 20 -> 20

从0开始,每次在前面的基础上加上3,如果加3后的值超出了最大值,择选取未使用过的最小值。而3则是行数。这样的每次计算前还得计算出行数,挺麻烦的。
将矩阵倒过来,由矩阵2转成矩阵1,观察发现规律:

原来位置->目标位置 原来位置->目标位置 原来位置->目标位置 原来位置->目标位置 原来位置->目标位置 原来位置->目标位置 原来位置->目标位置
0 -> 0 3 -> 1 6 -> 2 9 -> 3 12 -> 4 15 -> 5 18 -> 6
1 -> 7 4 -> 8 7 -> 9 10 -> 10 13 -> 11 16 -> 12 19 -> 13
2 -> 14 5 -> 15 8 -> 16 11 -> 17 14 -> 18 17 -> 19 20 -> 20

从0开始,每次在前面的基础上加上7,如果加7后的值超出了最大值,择选取未使用过的最小值。7是列数这就方便很多了。具体代码如下:

 /**
     矩阵转换

     - parameter arr: 原来矩阵
     - parameter column: 列数
     - returns: 装置矩阵
     */
    func matrixTransformation(arr:[String],column:Int) -> [String] {
        var matrix:[String] = []
        var min = 1
        var preNum = -column
        for _ in 0..= arr.count {
                num = min
                min += 1
            }
            matrix.append(arr[num])
            preNum = num

        }
        return matrix
    }

运行后效果达到我们的预期。

附上Dome一份:https://github.com/MRCaoHH/HHEmojiKeyboard

参考资料:
[1] http://emojipedia.org
[2] http://www.baike.com/wiki/emoji
[3] https://zh.wikipedia.org/wiki/繪文字
[4] http://www.unicode.org/emoji/charts/full-emoji-list.html#1f600
[5] http://punchdrunker.github.io/iOSEmoji/table_html/index.html

转载于:https://my.oschina.net/mexiaobai1315/blog/1486828

你可能感兴趣的:(iOS emoji应用(一))