美文网首页Swift编程Swift开发笔记Swift基础
swift5.0判断字符串中是否含有emoji表情的那些坑

swift5.0判断字符串中是否含有emoji表情的那些坑

作者: JQShan1993 | 来源:发表于2019-12-09 21:33 被阅读0次

    字符和字形的关系可能会有些混乱。我们将深入探讨使用表情符号和Swift处理它们的方式。假设您要检查一个字符串是否包含一个或多个表情符号,你将如何处理?

    背景

    表情符号是电子消息和网页中使用的表意文字和笑脸。表情符号存在各种类型,包括面部表情,常见对象,天气的地点和类型以及动物。

    尽管表情符号在2010年在全球范围内受到欢迎,但自1997年以来已经在日本使用。表情符号集最初由少于80个符号组成,现已增长到包含1200多个图标。

    2010年也是将第一套Emoji添加到Unicode标准的一年。Unicode是旨在统一处理和呈现文本的行业标准。它还包含来自世界各地的书写系统的字符索引,包括当前和古代的字符索引。该标准不断增长,版本12.1包含近138,000个字符。

    该标准不仅包括来自世界各地的字母表中的字符,而且还包括看不见且不能单独使用的特殊字符。我们稍后再讨论。强烈建议您查看unicode-table.com,以了解其规模。只需向下滚动主页上的表格即可发现各种组合和可能性。
    这是Unicode标准中定义的一些字符示例

    深入

    Unicode标准定义的每个字符都有一个十六进制标识符(Unicode码),并且字符被分为块,例如希伯来语或阿拉伯语。
    了解字符,字形和标量之间的区别很重要。UnicodeUnicode数字指定的字符组成。屏幕上可能不显示字符。同样,组合或字符可能会导致屏幕上出现一个字符。Swift通过对术语进行细微的区分来区分它们。这是一个非常复杂的故事,但要点是:

    • 字符串由字符组成
    • 字符由unicode标量组成
    • 每个Unicode标量代表一个Unicode字符

    回到Unicode字符。下面是一个例子:笑脸(😀)被识别为U + 1F600并且是表情段的一部分。*你可以通过几种方式在Swift字符串中表示表情符号:

    let smiley1 = "😀" 
    let smiley2 = "\u{1F600}" // Hex code, also "😀"
    

    看到这里,有人说“因此,我们可以找到表情符号的unicode段,并检查字符是否来自该段?”

    然而,表情符号字符并不只有一个段,运输和地图补充符号和象形文字有单独的段,其他符号和象形文字中有很多图标。
    即使我们确定哪些段或哪些字符列表是emoji表情,也不是长久之计。该标准在不断发展和扩展。

    将此应用于代码

    在Swift 4.2及之前的版本中,我们一直在尝试通过检查Unicode数字是否属于预定义的Unicode段之一来确定字符是否为表情符号。

    extension String {
        var containsEmoji: Bool {
            for scalar in unicodeScalars {
                switch scalar.value {
                case 0x1F600...0x1F64F, // Emoticons
                     0x1F300...0x1F5FF, // Misc Symbols and Pictographs
                     0x1F680...0x1F6FF, // Transport and Map
                     0x2600...0x26FF,   // Misc symbols
                     0x2700...0x27BF,   // Dingbats
                     0xFE00...0xFE0F,   // Variation Selectors
                     0x1F900...0x1F9FF, // Supplemental Symbols and Pictographs
                     0x1F1E6...0x1F1FF: // Flags
                    return true
                default:
                    continue
                }
            }
            return false
        }
    }
    

    随之而来的是Swift 5.0,它带有一个新的Unicode.Scalar.Properties类,它为我们提供了一系列标志,以帮助我们弄清正在处理的内容。我们可以很容易地获取表示我们字符串的Unicode标量数组。
    下面举个简单的例子:

    // emoji表情
    let smiley = "😀"
    
    //  获取字符串标量
    let scalars = smiley.unicodeScalars // UnicodeScalarView instance
    
    // 我们只有一个字符,因此我们用first得到他
    let firstScalar = scalars.first // is 128512
    
    // 注意128512实际上是1F600(十六进制)的十进制(😀的unicode标识)
    
    // 获取属性
    let properties = firstScalar?.properties
    
    // 检查它是不是emoji表情
    let isEmoji = properties?.isEmoji // = true
    

    是不是这样就可以了呢?当然不是,比如:

    // 这里有个坑,这将会返回true
    "3".unicodeScalars.first?.properties.isEmoji
    

    这是因为标量“ 3” 可以表示为表情符号。属性isEmoji确实以这种方式引起误解。幸运的是,还有另一个属性:

    // 这将像以前一样返回true:
    "😀" .unicodeScalars.first?.properties.isEmojiPresentation
     
    // 这将返回false,就像我们期望的那样:
    "3" .unicodeScalars.first?.properties.isEmojiPresentation 
    
    //不幸的是,这并不适用于所有表情符号:
    "🌶" .unicodeScalars.first?.properties.isEmojiPresentation  //否
    "🌶" .unicodeScalars.first?.properties.generalCategory == .some(.otherSymbol)// true
    

    我们还没有真正的成功。实际上还有一些字符由多个字形组成。看看我们如何使用unicodeScalars.first?
    请考参考以下示例:

    "1️⃣".unicodeScalars.first?.properties.isEmojiPresentation //false
    "♦️".unicodeScalars.first?.properties.isEmojiPresentation //false
    "👍🏻".unicodeScalars.first?.properties.isEmojiPresentation //true
    "👨‍👩‍👧‍👧".unicodeScalars.first?.properties.isEmojiPresentation // true
    

    为了解释为什么会发生这种情况,让我们看一下unicodeScalars属性。该属性unicodeScalars返回的实例UnicodeScalarView。它的debugDescription只会产生原始的String,因此直接检查内容(或记录它)并不能提供太多的见解。幸运的是,有一个map函数将返回一个常规数组,因此我们最终得到了一个元素数组:Unicode.Scalar

    // 创建一个UnicodeScalarView 
    let scalarView = "1️⃣".unicodeScalars
    // 映射视图,以便我们得到一个常规数组,可以检查
    let scalars = scalarView.map { $0 }
    

    结果包含三个值:

    我们前面提到了那些特殊的标量。因此,这些字符的组合用于形成表情符号,将常规数字1变成该符号。第二和第三标量修改了初始标量。
    为了明确起见,您还可以使用十六进制unicode标识符手动创建此组合:

    “ \ u {0031}” //变成:1 
    “ \ u {0031} \ u {20E3}” //变成:1⃣ 
    “ \ u {0031} \ u {FE0F} \ u {20E3}” //变成:1️⃣
    

    同样,其他表情符号可以组合:

    //黑色钻石套装表情符号
    " \ u {2666}" //♦ 
    //添加'Variation Selector-16':
    " \ u {2666} \ u {FE0F}" //♦️ 
    
    //竖起大拇指标志:
    " \ u {1F44D}" //👍 
    //添加'表情符号修饰符Fitzpatrick Type-4':
    " \ u {1F44D} \ u {1F3FD}" //👍🏽 
    
    //男人,女人,女孩,男孩
    " \ u {1F468} \ u {1F469} \ u {1F467} \ u {1F466}" //👨👩👧👦 
    // 在每个标量之间添加空格对应标量,这是将7个标量组合成一个字符。
    " \ u {1F468} \ u {200D} \ u {1F469} \ u ” {200D} \ u {1F467} \ u {200D} \ u {1F466}" // 👨‍👩‍👧‍👦
    

    最后,请注意,并非每个由多个标量组成的字符都是一个表情符号:

    "\∪{} 0061" //字母:a
    "\∪{} 0302" //抑扬音: ^
    "\∪{0061} \∪{} 0302" //组合成:①
    

    小提示:也许您已经看到在线的消息/文本看起来很混乱。这通常称为Zalgo,实际上仅由许多Unicode字符组成,这些字符被合并为屏幕上的单个字符:

    let lotsOfScalars =“ E̵͉͈̥̝͛͊̂͗͊̈́̄͜” 
    let scalars = lotsOfScalars.unicodeScalars.map {$ 0} 
    // 合并为字符串,并添加空格以单独查看它们
    // //结果为: E  ̵  ͛  ͊  ̂  ͗  ͊  ̈́  ̄  ͜  ͉  ͈  ̥  ̝
    scalarList = scalars.reduce("",{"\($ 0)\($ 1)"})
    

    最终结论

    让我们结合这些信息,向CharacterString类添加一些帮助属性。我们会:

    • 检查一个字符是否恰好是将作为表情符号显示的一个标量
    • 检查一个字符是否由多个标量组成,这些标量将被组合成一个表情符号。
    extension Character {
        /// 简单的emoji是一个标量,以emoji的形式呈现给用户
        var isSimpleEmoji: Bool {
            guardletfirstProperties =unicodeScalars.first?.propertieselse{
                returnfalse
            }
            return unicodeScalars.count == 1 &&
                (firstProperties.isEmojiPresentation||
                    firstProperties.generalCategory==.otherSymbol)
        }
    
        /// 检查标量是否将合并到emoji中
        var isCombinedIntoEmoji: Bool {
            return unicodeScalars.count > 1 &&
                unicodeScalars.contains { $0.properties.isJoinControl || $0.properties.isVariationSelector }
        }
    
        /// 是否为emoji表情
        /// - Note: http://stackoverflow.com/questions/30757193/find-out-if-character-in-string-is-emoji
        varisEmoji:Bool{
            return isSimpleEmoji || isCombinedIntoEmoji
        }
    }
    

    接下来,我们将一些计算的属性添加到String来访问我们的Character扩展:

    extension String {
        /// 是否为单个emoji表情
        var isSingleEmoji: Bool {
            returncount==1&&containsEmoji
        }
    
        /// 包含emoji表情
        var containsEmoji: Bool {
            returncontains{ $0.isEmoji}
        }
    
        /// 只包含emoji表情
        var containsOnlyEmoji: Bool {
            return!isEmpty&&!contains{!$0.isEmoji}
        }
    
        /// 提取emoji表情字符串
        var emojiString: String {
            returnemojis.map{String($0) }.reduce("",+)
        }
    
        /// 提取emoji表情数组
        varemojis: [Character] {
            returnfilter{ $0.isEmoji}
        }
    
        /// 提取单元编码标量
        var emojiScalars: [UnicodeScalar] {
            returnfilter{ $0.isEmoji}.flatMap{ $0.unicodeScalars}
        }
    }
    

    现在检查我们的字符串中的表情符号变得非常简单:

    "A̛͚̖".containsEmoji // false
    "3".containsEmoji // false
    "A̛͚̖▶️".unicodeScalars // [65, 795, 858, 790, 9654, 65039]
    "A̛͚̖▶️".emojiScalars // [9654, 65039]
    "3️⃣".isSingleEmoji // true
    "3️⃣".emojiScalars // [51, 65039, 8419]
    "👌🏿".isSingleEmoji // true
    "🙎🏼‍♂️".isSingleEmoji // true
    "👨‍👩‍👧‍👧".isSingleEmoji // true
    "👨‍👩‍👧‍👧".containsOnlyEmoji // true
    "Hello 👨‍👩‍👧‍👧".containsOnlyEmoji // false
    "Hello 👨‍👩‍👧‍👧".containsEmoji // true
    "👫 Héllo 👨‍👩‍👧‍👧".emojiString // "👫👨‍👩‍👧‍👧"
    "👨‍👩‍👧‍👧".count // 1
    "👫 Héllœ 👨‍👩‍👧‍👧".emojiScalars // [128107, 128104, 8205, 128105, 8205, 128103, 8205, 128103]
    "👫 Héllœ 👨‍👩‍👧‍👧".emojis // ["👫", "👨‍👩‍👧‍👧"]
    "👫 Héllœ 👨‍👩‍👧‍👧".emojis.count // 2
    "👫👨‍👩‍👧‍👧👨‍👨‍👦".isSingleEmoji // false
    "👫👨‍👩‍👧‍👧👨‍👨‍👦".containsOnlyEmoji // true
    

    总结一下

    字符和标量之间有一个重要的区别。基本上,先定义标量的字符串,然后由系统渲染该字符串以确定标量将显示哪些字符。

    英文好的的童鞋可以看这里原文链接,虽然Unicode将每个代码点定义为字符,但是Swift确实会调用这些标量,并将术语“字符”用于标量的组合,这可能会导致字符串中出现单个字形。我觉得,因此诸如控制字符(即“ null ”和“ backspace ”)之类将被计为一个单独的字符。

    相关文章

      网友评论

        本文标题:swift5.0判断字符串中是否含有emoji表情的那些坑

        本文链接:https://www.haomeiwen.com/subject/fyjigctx.html