判断字符串中的字符是否是表情符号?

Find out if Character in String is emoji?

我需要找出字符串中的字符是否是表情符号。

比如我有这个字符:

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

我需要查明那个字符是不是表情符号。

您可以这样使用 NSString-RemoveEmoji

if string.isIncludingEmoji {

}

您可以使用此代码 example or this 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"]

I created an small example project with the code above.

最简单、最干净、最迅速的方法是简单地检查字符串中每个字符的 Unicode 代码点与已知的表情符号和 dingbats 范围,如下所示:

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
    }

}

Swift3注:

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"

使用 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 字符串,在使用 valueForKey 之前必须将字符串转换为 AnyObject:

let str = "hello"

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

NSString header file 中找到的方法。

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
    }
}

这是我的修复,更新了范围。

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

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

  • 四个表情符号字符:
  • 在每个表情符号之间是一个特殊的字符,就像字符胶一样;参见 the specs for more info

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

  • 常规表情符号:
  • 肤色调节剂:

最后一个,字形1️⃣包含三个unicode字符:

所以在渲染字符时,生成的字形非常重要。

Swift 5.0 及更高版本使这个过程变得更加容易,并且摆脱了我们需要做的一些猜测。 Unicode.Scalar's new Property 类型有助于确定我们正在处理的内容。 然而,这些属性只有在检查字形中的其他标量时才有意义。这就是为什么我们要向 Character class 添加一些方便的方法来帮助我们。

更多详情,I wrote an article explaining how this works

对于 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 { [=10=].isEmoji } }

    var containsOnlyEmoji: Bool { !isEmpty && !contains { ![=10=].isEmoji } }

    var emojiString: String { emojis.map { String([=10=]) }.reduce("", +) }

    var emojis: [Character] { filter { [=10=].isEmoji } }

    var emojiScalars: [UnicodeScalar] { filter { [=10=].isEmoji }.flatMap { [=10=].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

For older Swift versions, check out this gist containing my old code.

对于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;
}

我遇到了同样的问题,最后做了 StringCharacter 扩展。

代码太长 post 因为它实际上在 CharacterSet 中列出了所有表情符号(来自官方 unicode 列表 v5.0)你可以在这里找到它:

https://github.com/piterwilson/StringEmoji

常量

让 emojiCharacterSet: CharacterSet

包含所有已知表情符号的字符集(如官方 Unicode 列表 5.0 http://unicode.org/emoji/charts-5.0/emoji-list.html 中所述)

字符串

var isEmoji: Bool { 获取 }

String 实例是否表示一个已知的单个表情符号字符

print("".isEmoji) // false
print("".isEmoji) // true
print("".isEmoji) // false (String is not a single Emoji)
var containsEmoji: Bool { get }

String 实例是否包含已知的 Emoji 字符

print("".containsEmoji) // false
print("".containsEmoji) // true
print("".containsEmoji) // true
var unicodeName: 字符串 { 获取 }

在 String

的副本上应用 kCFStringTransformToUnicodeName - CFStringTransform
print("á".unicodeName) // \N{LATIN SMALL LETTER A WITH ACUTE}
print("".unicodeName) // "\N{FACE WITH STUCK-OUT TONGUE AND WINKING EYE}"
var niceUnicodeName: String { get }

Returns kCFStringTransformToUnicodeName - CFStringTransform 的结果,删除了 \N{ 前缀和 } 后缀

print("á".unicodeName) // LATIN SMALL LETTER A WITH ACUTE
print("".unicodeName) // FACE WITH STUCK-OUT TONGUE AND WINKING EYE

字符

var isEmoji: Bool { 获取 }

Character 实例是否表示已知的表情符号字符

print("".isEmoji) // false
print("".isEmoji) // true

未来证明:手动检查角色的像素;随着新表情符号的添加,其他解决方案将失效(并且已经失效)。

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

多年来,随着 Apple 使用新方法添加新的表情符号(例如通过使用其他字符预先诅咒一个字符构建的肤色表情符号)等,这些表情符号检测解决方案不断出现问题。

我终于崩溃了,只写了以下适用于所有当前表情符号的方法,并且应该适用于所有未来的表情符号。

该解决方案创建了一个带有字符和黑色背景的 UILabel。然后 CG 拍摄标签的快照,我扫描快照中的所有像素以查找任何非纯黑色像素。我添加黑色背景的原因是为了避免由于 Subpixel Rendering

而导致的错误着色问题

该解决方案在我的设备上运行得非常快,我每秒可以检查数百个字符,但应该注意的是,这是一个 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;
    
}

与我之前写的答案完全相似,但更新了一组表情符号标量。

extension String {
    func isContainEmoji() -> Bool {
        let isContain = unicodeScalars.first(where: { [=10=].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
        }
    }

}

Swift 5.0

…引入了一种新的检查方法!

您必须将 String 分解为 Scalars. Each Scalar has a Property value which supports the isEmoji 值!

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

您可能需要考虑检查 isEmojiPresentation 而不是 isEmoji,因为 Apple 对 isEmoji 声明如下:

This property is true for scalars that are rendered as emoji by default and also for scalars that have a non-default emoji rendering when followed by U+FE0F VARIATION SELECTOR-16. This includes some scalars that are not typically considered to be emoji.


这种方式实际上将表情符号拆分为所有修饰符,但处理起来更简单。由于 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 指出了一种获取所有表情符号的有趣方法:

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)
    }
}

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

我们可以查看变量 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

上述任务有一个很好的 。但是检查 unicode 标量的 Unicode.Scalar.Properties 对单个字符有好处。对于字符串不够灵活。

我们可以改用正则表达式——更通用的方法。下面详细描述了它是如何工作的。这是解决方案。

解决方案

在 Swift 中,您可以检查字符串是否是单个表情符号字符,使用具有此类计算 属性:

的扩展名
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 specification 定义了几种可能的 Emoji 字符表示形式。

单字符表情符号

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

Unicode 将 Emoji 字符定义为:

emoji_character := \p{Emoji}

但这并不一定意味着这样的角色会被画成Emoji。一个普通的数字符号“1”的 Emoji 属性 为真,尽管它仍然可能被绘制为文本。并且有一个这样的符号列表:#、©、4 等。

应该想到,我们可以使用额外的属性来检查:“Emoji_Presentation”。但它不是这样工作的。有一个像 或 的表情符号,它有 属性 Emoji_Presentation=false.

为了确保默认情况下字符被绘制为表情符号,我们应该检查其类别:它应该是“Other_symbol”。

所以,实际上单字符表情符号的正则表达式应该定义为:

emoji_character := \p{Emoji}&&\p{Other_symbol}

表情符号呈现顺序

一个字符,通常可以绘制为文本或表情符号。它的外观取决于一个特殊的跟随符号,一个表示它的表示类型的表示选择器。 \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}

表情符号键帽序列

该序列看起来与 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}

表情符号标签序列 (ETS)

一个使用所谓的 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 允许标签基于 emoji_modifier_sequence 或 emoji_presentation_sequence 在 ED-14a. But at the same time in regular expressions provided at the same documentation 他们似乎只检查基于单个表情符号字符的序列。

在 Unicode 12.1 表情符号列表中只定义了 three such Emojis。它们都是英国国家的国旗:英格兰、苏格兰和威尔士。所有这些都基于一个表情符号字符。所以,我们最好只检查这样一个序列。

正则表达式:

\p{Emoji} [\x{E0020}-\x{E007E}]+ \x{E007F}

表情符号零宽度连接器序​​列(ZWJ 序列)

零宽度连接符是标量 \x{200D}。在它的帮助下,几个本身已经是表情符号的字符可以组合成新的。

举个例子,“一家有父子女”的表情包‍‍是由爸爸、女儿、儿子的表情包和ZWJ符号粘在一起的组合。

允许将单个 Emoji 字符、Presentation 和 Modifier 序列的元素粘在一起。

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

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}] 
    ) 
  )*

使用以下扩展,

extension Character {
    var isSimpleEmoji: Bool {
        guard let firstScalar = unicodeScalars.first else {
            return false
        }
        return firstScalar.properties.isEmoji && firstScalar.value > 0x238C
    }  

    var isCombinedIntoEmoji: Bool {
        unicodeScalars.count > 1 && unicodeScalars.first?.properties.isEmoji ?? false
    }

    var isEmoji: Bool { isSimpleEmoji || isCombinedIntoEmoji }
}

extension String {
    var containsEmoji: Bool {
        contains(where: { [=10=].isEmoji })
    }
}

如何使用

let str = ""
print(str.containsEmoji) // true

reference 的原始回答。

原生一行代码

"❤️".unicodeScalars.contains { [=10=].properties.isEmoji } // true

作品来自 Swift5.0

extension String {
    // Returns false for if string contains characters like "Á‍‍1️⃣"
    var hasRestrictedCharacter: Bool {
        contains { ![=10=].isASCII }
    }
}

let testChars = " d1/Á‍‍1️⃣"

for char in testChars {
    let value = "\(char)".hasRestrictedCharacter
    print("\(char) : \(value)")
}

//  : false
//d : false
//1 : false
/// : false
//Á : true
// : true
//‍‍ : true
// : true
//1️⃣ : true

Swift 5 使用 Scalars 的解决方案适用于文本、笑脸、心形表情符号 ❤️❤️‍ 和数字 0️⃣ 1 2 3 等等

Swift 5 标量 具有 isEmojiisEmojiPresentation 属性,可帮助查找表情符号,尤其是字符串。

isEmoji - Boolean value indicating whether the scalar has an emoji presentation, whether or not it is the default.

isEmojiPresentation - A Boolean value indicating whether the scalar is one that should be rendered with an emoji presentation, rather than a text presentation, by default.

正如您从这些定义中看到的,我们不能只在字符串的标量上使用 isEmojiisEmojiPresentation - 这不会告诉我们这个标量是否真的 一个表情符号

幸运的是苹果给了我们一个线索:

testing isEmoji alone on a single scalar is insufficient to determine if a unit of text is rendered as an emoji; a correct test requires inspecting multiple scalars in a Character. In addition to checking whether the base scalar has isEmoji == true, you must also check its default presentation (see isEmojiPresentation) and determine whether it is followed by a variation selector that would modify the presentation.

所以最后这是我的实现,适用于数字、笑脸、文本和 ❤️ 符号:

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