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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/30757193/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 06:20:11  来源:igfitidea点击:

Find out if Character in String is emoji?

iosstringswiftcharacteremoji

提问by Andrew

I need to find out whether a character in a string is an emoji.

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

For example, I have this character:

例如,我有这个角色:

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

I need to find out if that character is an emoji.

我需要找出那个角色是否是表情符号。

回答by Kevin R

What I stumbled upon is the difference between characters, unicode scalars and glyphs.

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

For example, the glyph ??? consists of 7 unicode scalars:

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

  • Four emoji characters:
  • In between each emoji is a special character, which works like character glue; see the specs for more info
  • 四个表情符号:
  • 每个表情符号之间都有一个特殊字符,就像字符胶水一样;查看规格了解更多信息

Another example, the glyph consists of 2 unicode scalars:

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

  • The regular emoji:
  • A skin tone modifier:
  • 常规表情符号:
  • 肤色修饰符:

Last one, the glyph 1?? contains three unicode characters:

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

So when rendering the characters, the resulting glyphs really matter.

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

Swift 5.0 and above makes this process much easier and gets rid of some guesswork we needed to do. Unicode.Scalar's new Propertytype helps is determine what we're dealing with. However, those properties only make sense when checking the other scalars within the glyph. This is why we'll be adding some convenience methods to the Character class to help us out.

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

For more detail, I wrote an article explaining how this works.

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

For Swift 5.0, it leaves you with the following result:

对于 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 { 
"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
.isEmoji } } var containsOnlyEmoji: Bool { !isEmpty && !contains { !
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
    }

}
.isEmoji } } var emojiString: String { emojis.map { String(
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
    }
}
) }.reduce("", +) } var emojis: [Character] { filter {
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
.isEmoji } } var emojiScalars: [UnicodeScalar] { filter {
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)
    }
}
.isEmoji }.flatMap {
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)
    }
}
.unicodeScalars } } }

Which will give you the following results:

这将为您提供以下结果:

"hey".containsEmoji() //false

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

"".containsEmoji() //true
"".containsOnlyEmojis() //true

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

对于较旧的 Swift 版本,请查看包含我的旧代码的要点。

回答by Arnold

The simplest, cleanest, and swiftiestway to accomplish this is to simply check the Unicode code points for each character in the string against known emoji and dingbats ranges, like so:

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

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

回答by Sebastian Lopez

let str: NSString = "hello"

This is my fix, with updated ranges.

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

回答by alexkaessner

Swift 5.0

斯威夫特 5.0

… introduced a new way of checking exactly this!

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

You have to break your Stringinto its Scalars. Each Scalarhas a Propertyvalue which supports the isEmojivalue!

你必须打破你StringScalars. 每一个Scalar都有一个Property支持isEmoji价值的价值!

Actually you can even check if the Scalar is a Emoji modifier or more. Check out Apple's documentation: https://developer.apple.com/documentation/swift/unicode/scalar/properties

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

You may want to consider checking for isEmojiPresentationinstead of isEmoji, because Apple states the following for isEmoji:

您可能需要考虑检查 forisEmojiPresentation而不是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.

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



This way actually splits up Emoji's into all the modifiers, but it is way simpler to handle. And as Swift now counts Emoji's with modifiers (e.g.: ???, ?, ) as 1 you can do all kind of stuff.

这种方式实际上将表情符号拆分为所有修饰符,但处理起来更简单。由于 Swift 现在将带有修饰符的表情符号(例如:???, ?, )计为 1,您可以执行各种操作。

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

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


NSHipsterpoints out an interesting way to get all Emoji's:

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

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

回答by Miniroo

With Swift 5 you can now inspect the unicode properties of each character in your string. This gives us the convenient isEmojivariable on each letter. The problem is isEmojiwill return true for any character that can be converted into a 2-byte emoji, such as 0-9.

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

We can look at the variable isEmojiand also check the for the presence of an emoji modifier to determine if the ambiguous characters will display as an emoji.

我们可以查看变量isEmoji并检查是否存在表情符号修饰符,以确定不明确的字符是否会显示为表情符号。

This solution should be much more future proof than the regex solutions offered here.

这个解决方案应该比这里提供的正则表达式解决方案更具前瞻性。

let str = "hello"

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

Giving us

给我们

#import "NSString+EMOEmoji.h"

回答by JAL

Swift 3 Note:

斯威夫特 3 注意:

It appears the cnui_containsEmojiCharactersmethod has either been removed or moved to a different dynamic library. _containsEmojishould still work though.

cnui_containsEmojiCharacters方法似乎已被删除或移至不同的动态库。 _containsEmoji不过应该还是可以的。

let example: NSString = "string???withemojis?" //string with emojis

let containsEmoji: Bool = example.emo_containsEmoji()

    print(containsEmoji)

// Output: ["true"]

Swift 2.x:

斯威夫特 2.x:

I recently discovered a private API on NSStringwhich exposes functionality for detecting if a string contains an Emoji character:

我最近发现了一个私有 API,NSString该API公开了用于检测字符串是否包含 Emoji 字符的功能:

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

With an objc protocol and unsafeBitCast:

使用 objc 协议和unsafeBitCast

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

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

With valueForKey:

valueForKey

##代码##

With a pure Swift string, you must cast the string as AnyObjectbefore using valueForKey:

对于纯 Swift 字符串,您必须像AnyObject使用之前一样转换字符串valueForKey

##代码##

Methods found in the NSString header file.

NSString 头文件中找到的方法。

回答by Gabriel.Massana

You can use this code exampleor this pod.

您可以使用此代码示例或此pod

To use it in Swift, import the category into the YourProject_Bridging_Header

要在 Swift 中使用它,请将类别导入 YourProject_Bridging_Header

##代码##

Then you can check the range for every emoji in your String:

然后您可以检查字符串中每个表情符号的范围:

##代码##

I created an small example project with the code above.

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

回答by Ankit Goyal

For Swift 3.0.2, the following answer is the simplest one:

对于 Swift 3.0.2,以下答案是最简单的:

##代码##

回答by Alex Shoshiashvili

The absolutely similar answer to those that wrote before me, but with updated set of emoji scalars.

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

##代码##

回答by Albert Renshaw

Future Proof: Manually check the character's pixels; the other solutions will break (and have broken) as new emojis are added.

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

Note: This is Objective-C (can be converted to Swift)

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

Over the years these emoji-detecting solutions keep breaking as Apple adds new emojis w/ new methods (like skin-toned emojis built by pre-cursing a character with an additional character), etc.

多年来,随着 Apple 添加具有新方法的新表情符号(例如通过使用附加字符预先诅咒字符而构建的肤色表情符号)等,这些表情符号检测解决方案不断失效。

I finally broke down and just wrote the following method which works for all current emojis and should work for all future emojis.

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

The solution creates a UILabel with the character and a black background. CG then takes a snapshot of the label and I scan all pixels in the snapshot for any non solid-black pixels. The reason I add the black background is to avoid issues of false-coloring due to Subpixel Rendering

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

The solution runs VERY fast on my device, I can check hundreds of characters a second, but it should be noted that this is a CoreGraphics solution and should not be used heavily like you could with a regular text method. Graphics processing is data heavy so checking thousands of characters at once could result in noticeable lag.

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

##代码##