找出字符串中的字符是否为表情符号?

134

我需要确定一个字符串中的字符是否为表情符号。

例如,我有这个字符:

let string = ""
let character = Array(string)[0]

我需要确定那个字符是否为表情符号。


我很好奇:你为什么需要那个信息? - Martin R
@EricD.:有很多Unicode字符需要多于一个UTF-8代码点(例如“€”= E2 82 AC)或多于一个UTF-16代码点(例如“” = D834 DD1E)。 - Martin R
1
希望您能从这个 obj-c 代码版本中得到灵感:https://dev59.com/DGIj5IYBdhLWcg3wx38x - Ashish Kakkad
字符串具有索引,这是使用它们的首选方式。要获取特定字符(或图形簇),可以执行以下操作: let character = string[string.index(after: string.startIndex)]let secondCharacter = string[string.index(string.startIndex, offsetBy: 1)] - Paul B
在这里我学到的重要一点是:"它是否是一个表情符号?"和"这个字符将以图片或文本形式呈现?"之间存在着区别。当很多人说"这是一个表情符号吗?"时,实际上他们想知道它是否会以图片形式呈现。举个例子,数字字符(例如"5")就是表情符号!但是!... 它们默认情况下以文本形式呈现。在iOS上,还有其他一些字符默认情况下是文本形式的表情符号,但是如果用户输入它们,几乎肯定会有一个图像变体选择器,因此通常会以图像形式显示,但不是默认情况下。 - Graham Lea
18个回答

327

我发现的是字符、Unicode标量和字形之间的区别。

例如,字形 ‍‍‍ 由7个Unicode标量组成:

  • 四个表情符号字符:
  • 在每个表情符号之间有一个特殊字符,它像字符粘合剂一样起作用;请参阅规格以获取更多信息

另一个例子,字形 由2个Unicode标量组成:

  • 常规表情符号:
  • 肤色修改器:

最后一个例子,字形 1️⃣ 包含三个Unicode字符:

因此,在呈现字符时,结果的字形非常重要。

Swift 5.0及以上版本使这个过程变得更加简单,并且消除了我们需要做出的一些猜测。 Unicode.Scalar的新Property类型有助于确定我们正在处理的是什么。 但是,只有在检查字形中的其他标量时,这些属性才有意义。这就是为什么我们将添加一些方便方法到Character类来帮助我们的原因。

有关更多详细信息,我写了一篇文章来解释这是如何工作的

对于Swift 5.0,这让您得到以下结果:

extension Character {
    /// A simple emoji is one scalar and presented to the user as an Emoji
    var isSimpleEmoji: Bool {
        guard let firstScalar = unicodeScalars.first else { return false }
        return firstScalar.properties.isEmoji && firstScalar.value > 0x238C
    }

    /// Checks if the scalars will be merged into an emoji
    var isCombinedIntoEmoji: Bool { unicodeScalars.count > 1 && unicodeScalars.first?.properties.isEmoji ?? false }

    var isEmoji: Bool { isSimpleEmoji || isCombinedIntoEmoji }
}

extension String {
    var isSingleEmoji: Bool { count == 1 && containsEmoji }

    var containsEmoji: Bool { contains { $0.isEmoji } }

    var containsOnlyEmoji: Bool { !isEmpty && !contains { !$0.isEmoji } }

    var emojiString: String { emojis.map { String($0) }.reduce("", +) }

    var emojis: [Character] { filter { $0.isEmoji } }

    var emojiScalars: [UnicodeScalar] { filter { $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
"⏰".isSingleEmoji // true
"".isSingleEmoji // true
"‍‍‍".isSingleEmoji // true
"".isSingleEmoji // true
"".containsOnlyEmoji // 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

如果您使用较旧的 Swift 版本,请查看此 gist 以获取我的旧代码。


8
这是目前为止这里最好且最正确的答案。谢谢!小提醒一下,你的例子与代码不匹配(片段中你将 containsOnlyEmoji 重命名为 containsEmoji - 我猜是因为更加正确,在我的测试中,它对于拥有混合字符的字符串返回了 true)。 - Tim Bull
3
我的错,我修改了一些代码,可能搞砸了。我更新了示例。 - Kevin R
2
@Andrew 这是一个非常混乱的地方。我添加了一个示例来说明如何做到这一点。问题在于,我必须假设通过检查字符来知道CoreText将如何呈现字形。如果有人有更清晰的方法,请告诉我。 - Kevin R
3
谢谢你指出这一点,我改变了“containsOnlyEmoji”检查的方式。我还将示例更新到Swift 3.0版。 - Kevin R
3
我还添加了比较语句:$0.properties.generalCategory == .otherSymbol,以便使其适用于更多的表情符号,例如⏰、🧱等。 - vicegax
显示剩余36条评论

64

最简单、最清晰、也是最快速的方法是,对于字符串中的每个字符,仅需检查其 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
    }

}

11
像这样的代码示例比建议包含第三方库依赖要好得多。Shardul的回答是不明智的建议-始终编写自己的代码。 - thefaj
太好了,感谢您对案例所涉及内容的评论。 - Shawn Throop
2
非常喜欢你的代码,我在这里的回答中实现了它(https://dev59.com/2Jfga4cB1Zd3GeqPC-7M)。我注意到一个问题是它缺少一些表情符号,可能是因为它们不属于你列出的类别之一,例如这个:机器人脸表情符号。 - Cue
1
@Tel 我猜应该是范围为0x1F900...0x1F9FF(根据维基百科)。不确定这个范围内的所有内容都应该被视为表情符号。 - Frizlab
谢谢。这很棒。是否有更新的新表情符号范围? - jonchoi

25

Swift 5.0

引入了一种全新的检查方法,可以准确地检查字符串中的表情符号!

您需要将String拆分为其Scalars。每个Scalar都有一个Property值,支持isEmoji值!

实际上,您甚至可以检查标量是否是表情符号修饰符或更多。请查看苹果的文档:https://developer.apple.com/documentation/swift/unicode/scalar/properties

您可能希望考虑检查isEmojiPresentation而不是isEmoji,因为苹果针对isEmoji给出了以下说明:

此属性对于默认呈现为表情符号的标量以及在其后跟随U+FE0F VARIATION SELECTOR-16时具有非默认表情符号呈现的标量为真。这包括一些通常不被视为表情符号的标量。


这种方法实际上将表情符号分成所有修饰符,但处理起来更加简单。由于Swift现在将带有修饰符的表情符号(例如:‍‍‍,‍)视为1,因此您可以进行各种操作。

var string = " test"

for scalar in string.unicodeScalars {
    let isEmoji = scalar.properties.isEmoji

    print("\(scalar.description) \(isEmoji)")
}

//  true
//   false
// t false
// e false
// s false
// t false

NSHipster指出了一种有趣的获取所有Emoji表情符号的方法:

import Foundation

var emoji = CharacterSet()

for codePoint in 0x0000...0x1F0000 {
    guard let scalarValue = Unicode.Scalar(codePoint) else {
        continue
    }

    // Implemented in Swift 5 (SE-0221)
    // https://github.com/apple/swift-evolution/blob/master/proposals/0221-character-properties.md
    if scalarValue.properties.isEmoji {
        emoji.insert(scalarValue)
    }
}

1
非常好的答案,谢谢。值得一提的是,您的最小SDK必须为10.2才能使用Swift 5的这一部分。此外,为了检查一个字符串是否仅由表情符号组成,我必须检查它是否具有以下属性之一: scalar.properties.isEmoji scalar.properties.isEmojiPresentation scalar.properties.isEmojiModifier scalar.properties.isEmojiModifierBase scalar.properties.isJoinControl scalar.properties.isVariationSelector - A Springham
10
注意,整数0-9被视为表情符号。因此,"6".unicodeScalars.first!.properties.isEmoji将被计算为true - Miniroo
2
还有其他像#*这样的字符也会被isEmoji检查返回为真。 isEmojiPresentation似乎工作更好,至少它会对英文-US键盘上的0...9*和任何其他符号返回false。有没有人对此有更多经验,并知道是否可以信任它进行输入验证? - Jan
3
❤️ 有两个标量。第一个标量的 isEmoji 属性为 true,但 isEmojiPresentation 属性为 false。第二个标量只有在 isVariationSelector 属性返回 true 时才会被视为表情符号。因此似乎没有直接的方式来理解什么是表情符号。 - zh.
为什么你的代码点循环顶部在0x1F0000处停止?最高合法的Unicode代码点(标量)值是0x10FFFF。因此,在上面的循环中,guard语句及其未成功构造Unicode.Scaler()的尝试会不必要地继续循环917,505次。或者你可能是想用break而不是continue。我错过了什么吗? - jsbox

12

使用 Swift 5,您现在可以检查字符串中每个字符的 Unicode 属性。这为我们提供了每个字母上方便的isEmoji 变量。问题是isEmoji 将对任何可以转换为2字节表情符号(如0-9)的字符返回true。

我们可以查看变量 isEmoji 并检查是否存在表情符号修饰符以确定模棱两可的字符是否将显示为表情符号。

与此处提供的正则表达式解决方案相比,这种解决方案应该更具未来性。

extension String {
    func containsEmoji() -> Bool {
        contains { $0.isEmoji }
    }

    func containsOnlyEmojis() -> Bool {
        return count > 0 && !contains { !$0.isEmoji }
    }
}

extension Character {
    // An emoji can either be a 2 byte unicode character or a normal UTF8 character with an emoji modifier
    // appended as is the case with 3️⃣. 0x203C is the first instance of UTF16 emoji that requires no modifier.
    // `isEmoji` will evaluate to true for any character that can be turned into an emoji by adding a modifier
    // such as the digit "3". To avoid this we confirm that any character below 0x203C has an emoji modifier attached
    var isEmoji: Bool {
        guard let scalar = unicodeScalars.first else { return false }
        return scalar.properties.isEmoji && (scalar.value >= 0x203C || unicodeScalars.count > 1)
    }
}

给我们

"hey".containsEmoji() //false

"Hello World ".containsEmoji() //true
"Hello World ".containsOnlyEmojis() //false

"3".containsEmoji() //false
"3️⃣".containsEmoji() //true

1
而更重要的是: Character("3️⃣").isEmoji // trueCharacter("3").isEmoji // false - Paul B
我认为第一个没有修饰符的UTF16表情符号是0x203C(双感叹号),而不是0x238C。我还认为你应该使用>=进行比较,而不是>。 - Marián Černý

8
extension String {
    func containsEmoji() -> Bool {
        for scalar in unicodeScalars {
            switch scalar.value {
            case 0x3030, 0x00AE, 0x00A9,// Special Characters
            0x1D000...0x1F77F,          // Emoticons
            0x2100...0x27BF,            // Misc symbols and Dingbats
            0xFE00...0xFE0F,            // Variation Selectors
            0x1F900...0x1F9FF:          // Supplemental Symbols and Pictographs
                return true
            default:
                continue
            }
        }
        return false
    }
}

这是我的修复,包括更新后的范围。

7

以下是使用Scalars的Swift 5解决方案,�应用�文本�笑脸😊,心形表情�����和数字0�⃣ 1 2 3等。

isEmoji�性和isEmojiPresentation�性�以帮助我们在给定的字符串中找到表情符�。

isEmoji - 布尔值,指示这个scalar是�有一个表情符�, 无论是�是默认值。

isEmojiPresentation - 布尔值,指示这个scalar是�应该以表情符�呈�, 而�是默认的文本呈�方�。

�这些定义中,我们�以看出,在字符串的标�上仅使用isEmoji或isEmojiPresentation是�够的 - 这�能告诉我们这个标�是�是一个真正的表情符�。

幸�的是,Apple为我们�供了一些线索:

仅仅使用isEmoji��独测试�个标�, 是�足以确定所检测到的文本�元是�呈�为表情符�的; 正确的测试需�检查Character中的多个标�。除了检查基础标�是�具有isEmoji == true, 还必须检查它的默认表示(请�阅isEmojiPresentation),并确定它是���带有�以修改呈�方�的�异选择器。

所以这里是我的��,适用�数字�笑脸,文本和��符�:

import Foundation

extension String {

    func containsEmoji() -> Bool {
        
        for character in self {
            var shouldCheckNextScalar = false
            for scalar in character.unicodeScalars {
               if shouldCheckNextScalar {
                    if scalar == "\u{FE0F}" { // scalar that indicates that character should be displayed as emoji
                        return true
                    }
                    shouldCheckNextScalar = false
                }
                
                if scalar.properties.isEmoji {
                    if scalar.properties.isEmojiPresentation {
                        return true
                    }
                    shouldCheckNextScalar = true
                }
            }
        }
        
        return false
    }
    
}

测试:

"hello ❤️".containsEmoji()   // true
"1234567890".containsEmoji() // false
"numero 0️⃣".containsEmoji()  // true
"abcde".containsEmoji()      // false
"panda ".containsEmoji()   // true

我认为这个解决方案接近理想,但是你应该检查文本变体字符(\u{FE0E}不在标量中,这样才能确定你的第一个测试用例是否为false,因为我看到的心形是非表情符号版本的❤️。 - Graham Lea

5

有一个很好的solution可以解决提到的问题。但是检查Unicode.Scalar.Properties对于单个字符非常好,而对于字符串来说则不够灵活。

我们可以使用正则表达式——更通用的方法。下面详细介绍了它的工作原理。这里是解决方案。

解决方案

在Swift中,您可以使用具有此类计算属性的扩展来检查String是否为单个Emoji字符:

extension String {

    var isSingleEmoji : Bool {
        if self.count == 1 {
            let emodjiGlyphPattern = "\\p{RI}{2}|(\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})|[\\p{Emoji}&&\\p{Other_symbol}])(\\x{200D}(\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})|[\\p{Emoji}&&\\p{Other_symbol}]))*"

            let fullRange = NSRange(location: 0, length: self.utf16.count)
            if let regex = try? NSRegularExpression(pattern: emodjiGlyphPattern, options: .caseInsensitive) {
                let regMatches = regex.matches(in: self, options: NSRegularExpression.MatchingOptions(), range: fullRange)
                if regMatches.count > 0 {
                    // if any range found — it means, that that single character is emoji
                    return true
                }
            }
        }
        return false
    }

}

如何工作(详细说明)

单个表情符号(图形符号)可以由多种不同的符号、序列及其组合来复制。Unicode规范定义了几种可能的表情符号字符表示。

单字符表情符号

由单个Unicode标量复制的表情符号字符。

Unicode将表情符号字符定义为:

emoji_character := \p{Emoji}

但这并��味�这样的字符一定会被绘制�表情符�。普通数字符�“1�的表情符��性为true,尽管它�然�能被绘制为文本。而且有一些这样的符�列表:#�©�4等。
人们�能认为,我们�以使用其他�性�检查:“Emoji_Presentation�。但�际上它并�是这样工作的。有一些表情符�,比如👨�👩�👧和🧟�♂�,它们的�性Emoji_Presentation=false。
为了确�字符默认情况下以表情符�形�绘制,我们应该检查它的类别:它应该是“Other_symbol�。
因此,�际上�字符表情符�的正则表达�应��定义为:
emoji_character := \p{Emoji}&&\p{Other_symbol}

Emoji展示序列

一个字符,通常可以被绘制为文本或表情符号。它的外观取决于一个特殊的后续符号,即展示选择器,它指示其展示类型。\x{FE0E} 定义了文本表示。 \x{FE0F} 定义了表情符号表示。

这些符号的列表可以在此处找到(https://unicode.org/Public/emoji/12.1/emoji-variation-sequences.txt)。

Unicode 将展示序列定义如下:

emoji_presentation_sequence := emoji_character emoji_presentation_selector

它的正则表达式序列:

emoji_presentation_sequence := \p{Emoji} \x{FE0F}

表情符号键帽序列

该序列与展示序列非常相似,但末尾有额外的标量:\x{20E3}。用于它的可能基本标量的范围相当狭窄:0-9#* ——仅此而已。例如:1️⃣,8️⃣,*️⃣。

Unicode将键帽序列定义为:

emoji_keycap_sequence := [0-9#*] \x{FE0F 20E3}

它的正则表达式:

emoji_keycap_sequence := \p{Emoji} \x{FE0F} \x{FE0F}

表情符号修改序列

一些表情符号可以具有修改外观的功能,例如肤色。例如表情符号 可以不同: 。要定义一个表情符号,在这种情况下称为“表情符号修改基础”,可以使用后续的“表情符号修改器”。

通常这样的序列看起来像这样:

emoji_modifier_sequence := emoji_modifier_base emoji_modifier

为了检测它,我们可以搜索一个正则表达式序列:
emoji_modifier_sequence := \p{Emoji} \p{EMod}

Emoji国旗序列

国旗是具有特定结构的表情符号。每个国旗用两个“区域指示器”符号表示。

Unicode将它们定义为:

emoji_flag_sequence := regional_indicator regional_indicator

例如,乌克兰的国旗实际上由两个标量表示:\u{0001F1FA \u{0001F1E6}
它的正则表达式为:
emoji_flag_sequence := \p{RI}{2}

Emoji标签序列(ETS)

使用所谓的标签基础(tag_base)后面跟随自定义标签规范的序列,由符号范围\x{E0020}-\x{E007E}组成,并以标签结束标记\x{E007F}结尾。

Unicode将其定义为:

emoji_tag_sequence := tag_base tag_spec tag_end
tag_base           := emoji_character
                    | emoji_modifier_sequence
                    | emoji_presentation_sequence
tag_spec           := [\x{E0020}-\x{E007E}]+
tag_end            := \x{E007F}

奇怪的是,Unicode允许标签基于ED-14a中的emoji_modifier_sequence或emoji_presentation_sequence。但同时,在文档中提供的正则表达式中,它们似乎仅基于单个Emoji字符来检查序列。
在Unicode 12.1 Emoji列表中,只定义了三个这样的Emoji。它们都是英国国旗:英格兰、苏格兰和威尔士的旗帜。而且它们都是基于单个Emoji字符的。因此,我们最好只检查这样的序列。
正则表达式:
\p{Emoji} [\x{E0020}-\x{E007E}]+ \x{E007F}

表情零宽连接器序列(ZWJ序列)

零宽连接器是一个标量 \x{200D}。通过它,几个已经是表情符号的字符可以组合成新的表情符号。

例如,“有父亲、儿子和女儿的家庭”表情符号 ‍‍ 可以由父亲、女儿和儿子表情符号用 ZWJ 符号粘合在一起重现。

允许将单个表情符号字符、表示和修饰序列粘在一起。

这种序列的正则表达式通常如下所示:

emoji_zwj_sequence := emoji_zwj_element (\x{200d} emoji_zwj_element )+

所有的正则表达式

上述提到的所有Emoji表示都可以用一个正则表达式描述:

\p{RI}{2}
| ( \p{Emoji} 
    ( \p{EMod} 
    | \x{FE0F}\x{20E3}? 
    | [\x{E0020}-\x{E007E}]+\x{E007F} 
    ) 
  | 
[\p{Emoji}&&\p{Other_symbol}] 
  )
  ( \x{200D}
    ( \p{Emoji} 
      ( \p{EMod} 
      | \x{FE0F}\x{20E3}? 
      | [\x{E0020}-\x{E007E}]+\x{E007F} 
      ) 
    | [\p{Emoji}&&\p{Other_symbol}] 
    ) 
  )*

你为什么要检查 self.count == 1 - Giorgio
@Giorgio 因为实现中的计算属性说明它是关于“单个”表情符号 :)。 - Dmytro Babych

4

Swift 3 注意:

cnui_containsEmojiCharacters 方法似乎已被删除或移动到另一个动态库。但是,_containsEmoji 应该仍然可以使用。

let str: NSString = "hello"

@objc protocol NSStringPrivate {
    func _containsEmoji() -> ObjCBool
}

let strPrivate = unsafeBitCast(str, to: NSStringPrivate.self)
strPrivate._containsEmoji() // true
str.value(forKey: "_containsEmoji") // 1


let swiftStr = "hello"
(swiftStr as AnyObject).value(forKey: "_containsEmoji") // 1

Swift 2.x:

我最近发现了一个在 NSString 上的私有 API,它可以用于检测字符串是否包含表情符号:

let str: NSString = "hello"

使用 Objective-C 协议和 unsafeBitCast

@objc protocol NSStringPrivate {
    func cnui_containsEmojiCharacters() -> ObjCBool
    func _containsEmoji() -> ObjCBool
}

let strPrivate = unsafeBitCast(str, NSStringPrivate.self)
strPrivate.cnui_containsEmojiCharacters() // true
strPrivate._containsEmoji() // true

使用 valueForKey 方法:
str.valueForKey("cnui_containsEmojiCharacters") // 1
str.valueForKey("_containsEmoji") // 1

使用纯Swift字符串时,您必须在使用valueForKey之前将字符串转换为AnyObject

let str = "hello"

(str as AnyObject).valueForKey("cnui_containsEmojiCharacters") // 1
(str as AnyObject).valueForKey("_containsEmoji") // 1

这里是在NSString头文件中发现的方法。


这正是我所寻找的,谢谢JAL。 - user5180348
这会被苹果拒绝吗? - Andrey Chernukha
@AndreyChernukha 总会有风险,但我还没有遇到过任何拒绝。 - JAL
永远不要使用私有API。充其量,伤害只会在明天或下个月到来。 - xaphod

3

未来可靠:手动检查字符像素;其他解决方案会因为新的表情符号而崩溃(已经出现过)。

注意:这是Objective-C(可以转换为Swift)

多年来,随着苹果添加了新的表情符号和新方法(如通过在字符前加上额外字符构建肤色表情符号),这些检测表情符号的解决方案不断破裂。

我最终放弃了,并编写了以下方法,它适用于所有当前的表情符号,并应该适用于所有未来的表情符号。

该解决方案创建一个带有字符和黑色背景的UILabel。然后CG对标签进行快照,我扫描快照中的所有像素以查找任何非纯黑色像素。我添加黑色背景的原因是避免由于子像素渲染而导致的误色问题。

该解决方案在我的设备上运行非常快,我可以每秒检查数百个字符,但需要注意的是,这是一个CoreGraphics解决方案,不能像普通文本方法那样频繁使用。图形处理是数据密集型的,因此一次检查数千个字符可能会导致明显的延迟。

-(BOOL)isEmoji:(NSString *)character {
    
    UILabel *characterRender = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 1, 1)];
    characterRender.text = character;
    characterRender.font = [UIFont fontWithName:@"AppleColorEmoji" size:12.0f];//Note: Size 12 font is likely not crucial for this and the detector will probably still work at an even smaller font size, so if you needed to speed this checker up for serious performance you may test lowering this to a font size like 6.0
    characterRender.backgroundColor = [UIColor blackColor];//needed to remove subpixel rendering colors
    [characterRender sizeToFit];
    
    CGRect rect = [characterRender bounds];
    UIGraphicsBeginImageContextWithOptions(rect.size,YES,0.0f);
    CGContextRef contextSnap = UIGraphicsGetCurrentContext();
    [characterRender.layer renderInContext:contextSnap];
    UIImage *capturedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    CGImageRef imageRef = [capturedImage CGImage];
    NSUInteger width = CGImageGetWidth(imageRef);
    NSUInteger height = CGImageGetHeight(imageRef);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    unsigned char *rawData = (unsigned char*) calloc(height * width * 4, sizeof(unsigned char));
    NSUInteger bytesPerPixel = 4;//Note: Alpha Channel not really needed, if you need to speed this up for serious performance you can refactor this pixel scanner to just RGB
    NSUInteger bytesPerRow = bytesPerPixel * width;
    NSUInteger bitsPerComponent = 8;
    CGContextRef context = CGBitmapContextCreate(rawData, width, height,
                                                 bitsPerComponent, bytesPerRow, colorSpace,
                                                 kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
    CGColorSpaceRelease(colorSpace);
    
    CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
    CGContextRelease(context);
    
    BOOL colorPixelFound = NO;
    
    int x = 0;
    int y = 0;
    while (y < height && !colorPixelFound) {
        while (x < width && !colorPixelFound) {
            
            NSUInteger byteIndex = (bytesPerRow * y) + x * bytesPerPixel;
            
            CGFloat red = (CGFloat)rawData[byteIndex];
            CGFloat green = (CGFloat)rawData[byteIndex+1];
            CGFloat blue = (CGFloat)rawData[byteIndex+2];
            
            CGFloat h, s, b, a;
            UIColor *c = [UIColor colorWithRed:red green:green blue:blue alpha:1.0f];
            [c getHue:&h saturation:&s brightness:&b alpha:&a];//Note: I wrote this method years ago, can't remember why I check HSB instead of just checking r,g,b==0; Upon further review this step might not be needed, but I haven't tested to confirm yet. 
            
            b /= 255.0f;
            
            if (b > 0) {
                colorPixelFound = YES;
            }
            
            x++;
        }
        x=0;
        y++;
    }
    
    return colorPixelFound;
    
}

6
我喜欢你的思维! ;) - 突破常规! - Ramon
你为什么要这样对我们做?#苹果 #Unicode标准 - d4Rk
我有一段时间没有看过这个了,但我想知道是否需要先转换为UIColor再转换为hsb;似乎我只需检查r、g、b是否都等于0即可?如果有人尝试,请告诉我。 - Albert Renshaw
我喜欢这个解决方案,但是如果出现像 ℹ 这样的字符,它不会出问题吗? - Juan Carlos Ospina Gonzalez
1
@JuanCarlosOspinaGonzalez 不是,以表情符号呈现为带有白色i的蓝色方框。不过这提出了一个很好的观点,即UILabel应该强制字体为“AppleColorEmoji”,现在将其添加为故障安全措施,尽管我认为Apple会默认为那些。 - Albert Renshaw

3
你可以使用这段代码example或这段代码pod
要在Swift中使用它,请将该类别导入到YourProject_Bridging_Header中。
#import "NSString+EMOEmoji.h"

然后,您可以检查字符串中每个表情符号的范围:
let example: NSString = "string‍‍‍withemojis✊" //string with emojis

let containsEmoji: Bool = example.emo_containsEmoji()

    print(containsEmoji)

// Output: ["true"]

我用上述代码创建了一个小示例项目。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接