简体   繁体   English

检测用户是否在 UITextView 中输入了表情符号

[英]Detect if a user has typed an emoji character in UITextView

I have a UITextView and I need to detect if a user enters an emoji character.我有一个 UITextView,我需要检测用户是否输入了表情符号。

I would think that just checking the unicode value of the newest character would suffice but with the new emoji 2s, some characters are scattered all throughout the unicode index (ie Apple's newly designed copyright and register logos).我认为只检查最新字符的 unicode 值就足够了,但是对于新的 emoji 2s,一些字符分散在整个 unicode 索引中(即 Apple 新设计的版权和注册标志)。

Perhaps something to do with checking the language of the character with NSLocale or LocalizedString values?也许与使用 NSLocale 或 LocalizedString 值检查字符的语言有关?

Does anyone know a good solution?有谁知道一个好的解决方案?

Thanks!谢谢!

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.该解决方案创建一个带有字符和黑色背景的 UILabel。 CG then takes a snapshot of the label and I scan all pixels in the snapshot for any non solid-black pixels.然后 CG 拍摄标签的快照,我扫描快照中的所有像素以查找任何非纯黑色像素。 The reason I add the black background is to avoid issues of false-coloring due to Subpixel Rendering我添加黑色背景的原因是为了避免由于子像素渲染导致的假色问题

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.该解决方案在我的设备上运行得非常快,我每秒可以检查数百个字符,但应该注意,这是一个 CoreGraphics 解决方案,不应像使用常规文本方法那样大量使用。 Graphics processing is data heavy so checking thousands of characters at once could result in noticeable lag.图形处理是大量数据,因此一次检查数千个字符可能会导致明显的延迟。

-(BOOL)isEmoji:(NSString *)character {

    UILabel *characterRender = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 1, 1)];
    characterRender.text = character;
    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;
    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];

            b /= 255.0f;

            if (b > 0) {
                colorPixelFound = YES;
            }

            x++;
        }
        x=0;
        y++;
    }

    return colorPixelFound;

}

First let's address your "55357 method" – and why it works for many emoji characters.首先让我们解决您的“55357 方法” ——以及为什么它适用于许多表情符号字符。

In Cocoa, an NSString is a collection of unichar s, and unichar is just a typealias for unsigned short which is the same as UInt16 .在 Cocoa 中, NSStringunichar的集合,而unichar只是unsigned short类型别名,与UInt16相同。 Since the maximum value of UInt16 is 0xffff , this rules out quite a few emoji from being able to fit into one unichar , as only two out of the six main Unicode blocks used for emoji fall under this range:由于UInt16的最大值是0xffff ,这就排除了很多 emoji 无法放入一个unichar ,因为用于 emoji 的六个主要 Unicode 块中只有两个在这个范围内:

These blocks contain 113 emoji, and an additional 66 emoji that can be represented as a single unichar can be found spread around various other blocks.这些块包含 113 个表情符号,另外 66 个可以表示为单个unichar表情符号可以在其他各种块中找到。 However, these 179 characters only represent a fraction of the 1126 emoji base characters , the rest of which must be represented by more than one unichar .但是,这 179 个字符仅代表1126 个 emoji 基本字符中的一小部分,其余字符必须由多个unichar

Let's analyse your code:让我们分析您的代码:

unichar unicodevalue = [text characterAtIndex:0];

What's happening is that you're simply taking the first unichar of the string, and while this works for the previously mentioned 179 characters, it breaks apart when you encounter a UTF-32 character, since NSString converts everything into UTF-16 encoding.发生的情况是您只是获取字符串的第一个unichar ,虽然这适用于前面提到的 179 个字符,但当您遇到 UTF-32 字符时它会分开,因为NSString将所有内容转换为 UTF-16 编码。 The conversion works by substituting the UTF-32 value with surrogate pairs , which means that the NSString now contains two unichar s.转换的工作原理是代理对替换 UTF-32 值,这意味着NSString现在包含两个unichar

And now we're getting to why the number 55357, or 0xd83d , appears for many emoji: when you only look at the first UTF-16 value of a UTF-32 character you get the high surrogate, each of which have a span of 1024 low surrogates.现在我们开始了解为什么数字 55357 或0xd83d出现在许多表情符号中:当您只查看 UTF-32 字符的第一个 UTF-16 值时,您会得到高代理,每个代理都有一个跨度1024个低代理。 The range for the high surrogate 0xd83d is U+1F400–U+1F7FF, which starts in the middle of the largest emoji block, Miscellaneous Symbols and Pictographs (U+1F300–U+1F5FF), and continues all the way up to Geometric Shapes Extended (U+1F780–U+1F7FF) – containing a total of 563 emoji, and 333 non-emoji characters within this range.高代理0xd83d的范围是 U+1F400–U+1F7FF,它从最大的表情符号块、 杂项符号和象形文字(U+1F300–U+1F5FF) 的中间开始,一直延续到几何形状扩展(U+1F780–U+1F7FF) – 总共包含 563 个表情符号,以及此范围内的 333 个非表情符号字符。

So, an impressive 50% of emoji base characters have the the high surrogate 0xd83d , but these deduction methods still leave 384 emoji characters unhandled, along with giving false positives for at least as many.因此,令人印象深刻的 50% 表情符号基本字符具有高代理0xd83d ,但这些推论方法仍然留下 384 个表情符号字符未处理,同时给出至少同样多的误报。


So, how can you detect whether a character is an emoji or not?那么,如何检测一个字符是否是 emoji 呢?

I recently answered a somewhat related question with a Swift implementation , and if you want to, you can look at how emoji are detected in this framework , which I created for the purpose of replacing standard emoji with custom images.我最近用 Swift 实现回答了一个有点相关的问题,如果你愿意,你可以看看在这个框架中如何检测表情符号,我创建它的目的是用自定义图像替换标准表情符号。

Anyhow, what you can do is extract the UTF-32 code point from the characters, which we'll do according to the specification :无论如何,您可以做的是从字符中提取 UTF-32 代码点,我们将根据规范进行操作

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {

    // Get the UTF-16 representation of the text.
    unsigned long length = text.length;
    unichar buffer[length];
    [text getCharacters:buffer];

    // Initialize array to hold our UTF-32 values.
    NSMutableArray *array = [[NSMutableArray alloc] init];

    // Temporary stores for the UTF-32 and UTF-16 values.
    UTF32Char utf32 = 0;
    UTF16Char h16 = 0, l16 = 0;

    for (int i = 0; i < length; i++) {
        unichar surrogate = buffer[i];

        // High surrogate.
        if (0xd800 <= surrogate && surrogate <= 0xd83f) {
            h16 = surrogate;
            continue;
        }
        // Low surrogate.
        else if (0xdc00 <= surrogate && surrogate <= 0xdfff) {
            l16 = surrogate;

            // Convert surrogate pair to UTF-32 encoding.
            utf32 = ((h16 - 0xd800) << 10) + (l16 - 0xdc00) + 0x10000;
        }
        // Normal UTF-16.
        else {
            utf32 = surrogate;
        }

        // Add UTF-32 value to array.
        [array addObject:[NSNumber numberWithUnsignedInteger:utf32]];
    }

    NSLog(@"%@ contains values:", text);

    for (int i = 0; i < array.count; i++) {
        UTF32Char character = (UTF32Char)[[array objectAtIndex:i] unsignedIntegerValue];
        NSLog(@"\t- U+%x", character);
    }

    return YES;
}

Typing "😎" into the UITextView writes this to console:UITextView输入“😎”会将其写入控制台:

😎 contains values:
    - U+1f60e

With that logic, just compare the value of character to your data source of emoji code points, and you'll know exactly if the character is an emoji or not.按照这种逻辑,只需将character的值与表情符号代码点的数据源进行比较,您就可以准确地知道该字符是否是表情符号。


PS聚苯乙烯

There are a few "invisible" characters, namely Variation Selectors and zero-width joiners , that also should be handled, so I recommend studying those to learn how they behave.有一些“隐形”字符,即Variation Selectorszero-width joiners ,也应该处理,所以我建议研究它们以了解它们的行为。

Another solution: https://github.com/woxtu/NSString-RemoveEmoji另一种解决方案: https : //github.com/woxtu/NSString-RemoveEmoji

Then, after import this extension, you can use it like this:然后,在导入这个扩展后,你可以像这样使用它:

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
    // Detect if an Emoji is in the string "text"
    if(text.isIncludingEmoji) {
        // Show an UIAlertView, or whatever you want here
        return NO;
    }

    return YES;
}

Hope that helps ;)希望有帮助;)

if your do not want your keyboard to show emoji you can use YOURTEXTFIELD/YOURTEXTVIEW.keyboardType = .ASCIICapable如果您不希望键盘显示表情符号,您可以使用YOURTEXTFIELD/YOURTEXTVIEW.keyboardType = .ASCIICapable
This will show a keyboard with no emoji这将显示一个没有表情符号的键盘

Here is the emoji detection method in Swift .这是Swift 中表情符号检测方法。 It works fine.它工作正常。 Hope it will help others.希望它能帮助别人。

 func isEmoji(_ character: String?) -> Bool {

        if character == "" || character == "\n" {
            return false
        }
        let characterRender = UILabel(frame: CGRect(x: 0, y: 0, width: 1, height: 1))
        characterRender.text = character
        characterRender.backgroundColor = UIColor.black  
        characterRender.sizeToFit()
        let rect: CGRect = characterRender.bounds
        UIGraphicsBeginImageContextWithOptions(rect.size, true, 0.0)

        if let contextSnap:CGContext = UIGraphicsGetCurrentContext() {
            characterRender.layer.render(in: contextSnap)
        }

        let capturedImage: UIImage? = (UIGraphicsGetImageFromCurrentImageContext())
        UIGraphicsEndImageContext()
        var colorPixelFound:Bool = false

        let imageRef = capturedImage?.cgImage
        let width:Int = imageRef!.width
        let height:Int = imageRef!.height

        let colorSpace = CGColorSpaceCreateDeviceRGB()

        let rawData = calloc(width * height * 4, MemoryLayout<CUnsignedChar>.stride).assumingMemoryBound(to: CUnsignedChar.self)

            let bytesPerPixel:Int = 4
            let bytesPerRow:Int = bytesPerPixel * width
            let bitsPerComponent:Int = 8

            let context = CGContext(data: rawData, width: Int(width), height: Int(height), bitsPerComponent: Int(bitsPerComponent), bytesPerRow: Int(bytesPerRow), space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue)



        context?.draw(imageRef!, in: CGRect(x: 0, y: 0, width: width, height: height))

            var x:Int = 0
            var y:Int = 0
            while (y < height && !colorPixelFound) {

                while (x < width && !colorPixelFound) {

                    let byteIndex: UInt  = UInt((bytesPerRow * y) + x * bytesPerPixel)
                    let red = CGFloat(rawData[Int(byteIndex)])
                    let green = CGFloat(rawData[Int(byteIndex+1)])
                    let blue = CGFloat(rawData[Int(byteIndex + 2)])

                    var h: CGFloat = 0.0
                    var s: CGFloat = 0.0
                    var b: CGFloat = 0.0
                    var a: CGFloat = 0.0

                    var c = UIColor(red:red, green:green, blue:blue, alpha:1.0)
                    c.getHue(&h, saturation: &s, brightness: &b, alpha: &a)

                    b = b/255.0

                    if Double(b) > 0.0 {
                        colorPixelFound = true
                    }
                    x+=1
                }
                x=0
                y+=1
            }

        return colorPixelFound
}

The following are cleaner and more efficient implementations of the code that checks to see if the drawn character has any color or not.以下是代码的更清晰、更有效的实现,用于检查绘制的字符是否具有任何颜色。

These have been written as category/extension methods to make them easier to use.这些已被编写为类别/扩展方法,以使其更易于使用。

Objective-C:目标-C:

NSString+Emoji.h: NSString+Emoji.h:

#import <Foundation/Foundation.h>

@interface NSString (Emoji)

- (BOOL)hasColor;

@end

NSString+Emoji.m: NSString+Emoji.m:

#import "NSString+Emoji.h"
#import <UIKit/UIKit.h>

@implementation NSString (Emoji)

- (BOOL)hasColor {
    UILabel *characterRender = [[UILabel alloc] initWithFrame:CGRectZero];
    characterRender.text = self;
    characterRender.textColor = UIColor.blackColor;
    characterRender.backgroundColor = UIColor.blackColor;//needed to remove subpixel rendering colors
    [characterRender sizeToFit];

    CGRect rect = characterRender.bounds;
    UIGraphicsBeginImageContextWithOptions(rect.size, YES, 1);
    CGContextRef contextSnap = UIGraphicsGetCurrentContext();
    [characterRender.layer renderInContext:contextSnap];
    UIImage *capturedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    CGImageRef imageRef = capturedImage.CGImage;
    size_t width = CGImageGetWidth(imageRef);
    size_t height = CGImageGetHeight(imageRef);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    size_t bytesPerPixel = 4;
    size_t bitsPerComponent = 8;
    size_t bytesPerRow = bytesPerPixel * width;
    size_t size = height * width * bytesPerPixel;
    unsigned char *rawData = (unsigned char *)calloc(size, sizeof(unsigned char));
    CGContextRef context = CGBitmapContextCreate(rawData, width, height,
                                                 bitsPerComponent, bytesPerRow, colorSpace,
                                                 kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
    CGColorSpaceRelease(colorSpace);

    CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
    CGContextRelease(context);

    BOOL result = NO;
    for (size_t offset = 0; offset < size; offset += bytesPerPixel) {
        unsigned char r = rawData[offset];
        unsigned char g = rawData[offset+1];
        unsigned char b = rawData[offset+2];

        if (r || g || b) {
            result = YES;
            break;
        }
    }

    free(rawData);

    return result;
}

@end

Example usage:用法示例:

if ([@"😎" hasColor]) {
    // Yes, it does
}
if ([@"@" hasColor]) {
} else {
    // No, it does not
}

Swift:迅速:

String+Emoji.swift:字符串+表情符号.swift:

import UIKit

extension String {
    func hasColor() -> Bool {
        let characterRender = UILabel(frame: .zero)
        characterRender.text = self
        characterRender.textColor = .black
        characterRender.backgroundColor = .black
        characterRender.sizeToFit()
        let rect = characterRender.bounds
        UIGraphicsBeginImageContextWithOptions(rect.size, true, 1)

        let contextSnap = UIGraphicsGetCurrentContext()!
        characterRender.layer.render(in: contextSnap)

        let capturedImageTmp = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        guard let capturedImage = capturedImageTmp else { return false }

        let imageRef = capturedImage.cgImage!
        let width = imageRef.width
        let height = imageRef.height

        let colorSpace = CGColorSpaceCreateDeviceRGB()

        let bytesPerPixel = 4
        let bytesPerRow = bytesPerPixel * width
        let bitsPerComponent = 8
        let size = width * height * bytesPerPixel
        let rawData = calloc(size, MemoryLayout<CUnsignedChar>.stride).assumingMemoryBound(to: CUnsignedChar.self)

        guard let context = CGContext(data: rawData, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue) else { return false }

        context.draw(imageRef, in: CGRect(x: 0, y: 0, width: width, height: height))

        var result = false
        for offset in stride(from: 0, to: size, by: 4) {
            let r = rawData[offset]
            let g = rawData[offset + 1]
            let b = rawData[offset + 2]

            if (r > 0 || g > 0 || b > 0) {
                result = true
                break
            }
        }

        free(rawData)

        return result
    }
}

Example usage:用法示例:

if "😎".hasColor() {
    // Yes, it does
}
if "@".hasColor() {
} else {
    // No, it does not
}

Swift's String type has a property .isEmoji Swift 的 String 类型有一个属性 .isEmoji

Best to check the documentation for the isEmojiPresentation caveat最好查看 isEmojiPresentation 警告的文档

https://developer.apple.com/documentation/swift/unicode/scalar/properties/3081577-isemoji https://developer.apple.com/documentation/swift/unicode/scalar/properties/3081577-isemoji

Well you can detect whether it only has ascii characters using this:好吧,您可以使用以下方法检测它是否只有 ascii 字符:

[myString canBeConvertedToEncoding:NSASCIIStringEncoding];

It will say no if it fails (or has emoji).如果失败(或有表情符号),它会说不。 Then you can do a if else statement that does not allow them to click enter or something.然后你可以做一个 if else 语句,不允许他们点击回车或其他东西。

Emoji characters length is 2 and so check if string length is 2 in method that is shouldChangeTextInRange: that is called after each key on keyboard hit表情符号字符长度为 2,因此检查 shouldChangeTextInRange 方法中的字符串长度是否为 2:在键盘敲击的每个键后调用

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text

{

    // Detect if an Emoji is in the string "text"
    if([text length]==2) {
        // Show an UIAlertView, or whatever you want here
        return YES;
    }
    else
{

       return NO;
}

} 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM