我需要找出字符串中的字符是否是表情符号。
例如,我有这个角色:
let string = "😀"
let character = Array(string)[0]
我需要确定该字符是否为表情符号。
我需要找出字符串中的字符是否是表情符号。
例如,我有这个角色:
let string = "😀"
let character = Array(string)[0]
我需要确定该字符是否为表情符号。
let character = string[string.index(after: string.startIndex)]
或 let secondCharacter = string[string.index(string.startIndex, offsetBy: 1)]
Answers:
我偶然发现的是字符,unicode标量和字形之间的区别。
例如,字形👨👨👧👧由7个unicode标量组成:
另一个示例,字形of由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
containsOnlyEmoji
检查方式。我还将示例更新为Swift 3.0。
最简单,最干净,最快捷的方法是按照已知的emoji和dingbats范围检查字符串中每个字符的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
}
}
0x1F900...0x1F9FF
(每个维基百科)。不确定是否应将所有范围都视为表情符号。
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
}
}
这是我的解决方法,具有更新的范围。
…引入了一种新的检查方法!
您必须闯入String
它Scalars
。每个Scalar
都有一个Property
支持该isEmoji
价值的价值!
实际上,您甚至可以检查Scalar是否是Emoji修改器或更多。查看Apple文档:https : //developer.apple.com/documentation/swift/unicode/scalar/properties
您可能需要考虑检查isEmojiPresentation
而不是isEmoji
,因为Apple指出以下内容isEmoji
:
此属性对于默认情况下渲染为表情符号的标量以及在后跟U + FE0F VARIATION SELECTOR-16时具有非默认表情符号渲染的标量均适用。这包括一些通常不被视为表情符号的标量。
这种方法实际上将表情符号拆分为所有修饰符,但处理起来更简单。随着Swift现在将带有修饰符(例如👨👩👧👦,👨🏻💻,🏴)的Emoji计数为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指出了一种获取所有表情符号的有趣方法:
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)
}
}
scalar.properties.isEmoji scalar.properties.isEmojiPresentation scalar.properties.isEmojiModifier scalar.properties.isEmojiModifierBase scalar.properties.isJoinControl scalar.properties.isVariationSelector
"6".unicodeScalars.first!.properties.isEmoji
将评估为true
使用Swift 5,您现在可以检查字符串中每个字符的unicode属性。这给我们isEmoji
每个字母方便的变量。问题是,isEmoji
对于任何可以转换为2字节表情符号的字符,例如0-9,都会返回true。
我们可以查看变量isEmoji
,还可以检查是否存在表情符号修饰符,以确定模糊字符是否将显示为表情符号。
与此处提供的正则表达式解决方案相比,该解决方案应具有更多的未来证明。
extension String {
func containsOnlyEmojis() -> Bool {
if count == 0 {
return false
}
for character in self {
if !character.isEmoji {
return false
}
}
return true
}
func containsEmoji() -> Bool {
for character in self {
if character.isEmoji {
return true
}
}
return false
}
}
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️⃣. 0x238C 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 0x238C has an emoji modifier attached
var isEmoji: Bool {
guard let scalar = unicodeScalars.first else { return false }
return scalar.properties.isEmoji && (scalar.value > 0x238C || unicodeScalars.count > 1)
}
}
给我们
"hey".containsEmoji() //false
"Hello World 😎".containsEmoji() //true
"Hello World 😎".containsOnlyEmojis() //false
"3".containsEmoji() //false
"3️⃣".containsEmoji() //true
Character("3️⃣").isEmoji // true
,同时Character("3").isEmoji // false
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:
我最近发现了一个私有API,在NSString
该API上公开了用于检测字符串是否包含表情符号字符的功能:
let str: NSString = "hello😊"
使用objc协议和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字符串,必须像AnyObject
在使用前那样强制转换字符串valueForKey
:
let str = "hello😊"
(str as AnyObject).valueForKey("cnui_containsEmojiCharacters") // 1
(str as AnyObject).valueForKey("_containsEmoji") // 1
在NSString头文件中找到的方法。
要在Swift中使用它,请将类别导入到 YourProject_Bridging_Header
#import "NSString+EMOEmoji.h"
然后,您可以检查字符串中每个表情符号的范围:
let example: NSString = "string👨👨👧👧with😍emojis✊🏿" //string with emojis
let containsEmoji: Bool = example.emo_containsEmoji()
print(containsEmoji)
// Output: ["true"]
多年来,随着苹果添加带有新方法的新表情符号(例如通过将其他字符预先模制一个字符而制成的肤色表情符号)等,这些检测表情符号的解决方案不断突破。
我终于崩溃了,只写了下面的方法,该方法适用于所有当前的表情符号,并且应该适用于所有未来的表情符号。
该解决方案创建一个带有字符和黑色背景的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;
}
AppleColorEmoji
,并且现在将其作为故障保护添加,尽管我认为苹果还是会默认使用这些字体
对于Swift 3.0.2,以下答案是最简单的答案:
class func stringContainsEmoji (string : NSString) -> Bool
{
var returnValue: Bool = false
string.enumerateSubstrings(in: NSMakeRange(0, (string as NSString).length), options: NSString.EnumerationOptions.byComposedCharacterSequences) { (substring, substringRange, enclosingRange, stop) -> () in
let objCString:NSString = NSString(string:substring!)
let hs: unichar = objCString.character(at: 0)
if 0xd800 <= hs && hs <= 0xdbff
{
if objCString.length > 1
{
let ls: unichar = objCString.character(at: 1)
let step1: Int = Int((hs - 0xd800) * 0x400)
let step2: Int = Int(ls - 0xdc00)
let uc: Int = Int(step1 + step2 + 0x10000)
if 0x1d000 <= uc && uc <= 0x1f77f
{
returnValue = true
}
}
}
else if objCString.length > 1
{
let ls: unichar = objCString.character(at: 1)
if ls == 0x20e3
{
returnValue = true
}
}
else
{
if 0x2100 <= hs && hs <= 0x27ff
{
returnValue = true
}
else if 0x2b05 <= hs && hs <= 0x2b07
{
returnValue = true
}
else if 0x2934 <= hs && hs <= 0x2935
{
returnValue = true
}
else if 0x3297 <= hs && hs <= 0x3299
{
returnValue = true
}
else if hs == 0xa9 || hs == 0xae || hs == 0x303d || hs == 0x3030 || hs == 0x2b55 || hs == 0x2b1c || hs == 0x2b1b || hs == 0x2b50
{
returnValue = true
}
}
}
return returnValue;
}
答案与之前写过的答案完全相似,但带有更新的表情符号标量集。
extension String {
func isContainEmoji() -> Bool {
let isContain = unicodeScalars.first(where: { $0.isEmoji }) != nil
return isContain
}
}
extension UnicodeScalar {
var isEmoji: Bool {
switch value {
case 0x1F600...0x1F64F,
0x1F300...0x1F5FF,
0x1F680...0x1F6FF,
0x1F1E6...0x1F1FF,
0x2600...0x26FF,
0x2700...0x27BF,
0xFE00...0xFE0F,
0x1F900...0x1F9FF,
65024...65039,
8400...8447,
9100...9300,
127000...127600:
return true
default:
return false
}
}
}
您可以像这样使用NSString-RemoveEmoji:
if string.isIncludingEmoji {
}
对于上述任务有一个很好的解决方案。但是检查Unicode标量的Unicode.Scalar.Properties对于单个字符是有好处的。而且对于Strings不够灵活。
我们可以改用正则表达式-更通用的方法。以下是其工作方式的详细说明。解决方案就在这里。
在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标量复制的Emoji字符。
Unicode将表情符号字符定义为:
emoji_character := \p{Emoji}
但这并不一定意味着将此类字符绘制为表情符号。普通数字符号“ 1”的Emoji表情属性为true,尽管它仍可能会绘制为文本。并且有这样的符号列表:#,©,4等。
应该认为,我们可以使用其他属性来检查:“ Emoji_Presentation”。但这不是这样的。有一个像🏟或🛍这样的表情符号,其属性为Emoji_Presentation = false。
为确保默认情况下将字符绘制为表情符号,我们应检查其类别:应为“ Other_symbol”。
因此,实际上,单字符表情符号的正则表达式应定义为:
emoji_character := \p{Emoji}&&\p{Other_symbol}
字符,通常可以绘制为文本或表情符号。它的外观取决于特殊的跟随符号,即表示选择器,它指示其表示类型。\ x {FE0E}定义文本表示。\ x {FE0F}定义表情符号表示。
此类符号的列表可以在[here]( 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}
该序列看起来与Presentation序列非常相似,但是在末尾具有附加的标量:\ 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_Base”),可以使用后续的“ Emoji_Modifier”。
通常,这样的顺序如下:
emoji_modifier_sequence := emoji_modifier_base emoji_modifier
为了检测它,我们可以搜索一个正则表达式序列:
emoji_modifier_sequence := \p{Emoji} \p{EMod}
标志是具有特定结构的表情符号。每个标志都用两个“ Regional_Indicator”符号表示。
Unicode对它们的定义如下:
emoji_flag_sequence := regional_indicator regional_indicator
例如,乌克兰🇺🇦的标志实际上用两个标量表示:\ u {0001F1FA \ u {0001F1E6}
正则表达式:
emoji_flag_sequence := \p{RI}{2}
使用所谓的tag_base的序列,其后是由符号范围\ x {E0020}-\ x {E007E}组成并由tag_end标记\ 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 。但是同时,在相同文档中提供的正则表达式中,它们似乎仅根据单个表情符号字符来检查序列。
在Unicode 12.1表情符号列表中,仅定义了三种此类表情符号。它们都是英国国家的国旗:英格兰,苏格兰和威尔士。所有这些都基于单个表情符号字符。因此,我们最好只检查这样的序列。
正则表达式:
\p{Emoji} [\x{E0020}-\x{E007E}]+ \x{E007F}
零宽度连接符是标量\ x {200D}。在它的帮助下,可以将几个本身已经是表情符号的字符组合成新的字符。
例如,“父亲,儿子和女儿的家庭”表情符号👨是通过将父亲👨,女儿👧和儿子👦表情符号与ZWJ符号粘合在一起的组合来复制的。
允许将元素(单个表情符号字符,表示和修饰符序列)粘贴在一起。
这种序列的正则表达式通常如下所示:
emoji_zwj_sequence := emoji_zwj_element (\x{200d} emoji_zwj_element )+
上面提到的所有表情符号表示都可以用一个正则表达式描述:
\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}]
)
)*
我遇到了同样的问题,最终提出了String
和Character
扩展。
该代码过长,无法发布,因为它实际上列出了所有表情符号(来自官方unicode列表v5.0),CharacterSet
您可以在此处找到它:
https://github.com/piterwilson/StringEmoji
包含所有已知表情符号的字符集(如官方Unicode列表5.0 http://unicode.org/emoji/charts-5.0/emoji-list.html中所述)
该String
实例是否代表一个已知的表情符号字符
print("".isEmoji) // false
print("😁".isEmoji) // true
print("😁😜".isEmoji) // false (String is not a single Emoji)
var containsEmoji:Bool {get}
String
实例是否包含已知的表情符号字符
print("".containsEmoji) // false
print("😁".containsEmoji) // true
print("😁😜".containsEmoji) // true
var unicodeName:字符串{get}
在字符串的副本上应用kCFStringTransformToUnicodeName
-CFStringTransform
print("á".unicodeName) // \N{LATIN SMALL LETTER A WITH ACUTE}
print("😜".unicodeName) // "\N{FACE WITH STUCK-OUT TONGUE AND WINKING EYE}"
var niceUnicodeName:字符串{get}
返回的结果kCFStringTransformToUnicodeName
-CFStringTransform
与\N{
前缀和}
后缀去掉
print("á".unicodeName) // LATIN SMALL LETTER A WITH ACUTE
print("😜".unicodeName) // FACE WITH STUCK-OUT TONGUE AND WINKING EYE
Character
实例是否代表已知的表情符号字符
print("".isEmoji) // false
print("😁".isEmoji) // true