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

wecizke3  于 2023-04-22  发布在  iOS
关注(0)|答案(9)|浏览(166)

我有一个UITextView,我需要检测用户是否输入了一个表情符号。
我认为只要检查最新字符的unicode值就足够了,但是对于新的emoji 2,一些字符分散在整个unicode索引中(即Apple新设计的版权和注册标志)。
也许是用NSLocale或LocalizedString值检查字符的语言?
有人知道一个好的解决方案吗?
谢谢!

g0czyy6m

g0czyy6m1#

多年来,这些表情符号检测解决方案不断突破,因为苹果增加了新的表情符号和新的方法(比如通过预先诅咒一个字符来构建肤色表情符号)等。
我终于崩溃了,只是写了下面的方法,适用于所有当前的表情符号,应该适用于所有未来的表情符号。
该解决方案创建了一个带有字符和黑色背景的UILabel。CG然后拍摄标签的快照,我扫描快照中的所有像素,以查找任何非纯黑色像素。我添加黑色背景的原因是为了避免由于Subpixel Rendering而导致的错误着色问题
该解决方案在我的设备上运行非常快,我可以每秒检查数百个字符,但应该注意的是,这是一个CoreGraphics解决方案,不应该像常规文本方法那样大量使用。图形处理是数据繁重的,因此一次检查数千个字符可能会导致明显的滞后。

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

*:如果苹果要推出一个纯黑色的表情符号,这个技术可以通过运行两次来改进,一次是黑色字体和黑色背景,然后再用白色字体和白色背景,并对结果进行OR运算。

os8fio9y

os8fio9y2#

首先,让我们解决你的“55357方法”* -以及为什么它适用于许多emoji字符。
在可可中,NSStringunichar的集合,而unichar只是unsigned short的类型别名,与UInt16相同。由于UInt160xffff,这排除了相当多的emoji能够适合一个unichar,因为用于emoji的六个主要Unicode块中只有两个属于这个范围:

这些区块包含113个表情符号,另外66个表情符号可以被表示为单个unichar,分布在其他区块中。然而,这179个字符只代表1126个表情符号基本字符的一小部分,其余的必须由多个unichar表示。
让我们分析一下你的代码:

unichar unicodevalue = [text characterAtIndex:0];

实际情况是,您只是获取字符串的第一个unichar,虽然这适用于前面提到的179个字符,但当您遇到UTF-32字符时,它会中断,因为NSString将所有内容转换为UTF-16编码。转换的工作原理是将UTF-32值替换为 surrogate pairs,这意味着NSString现在包含两个unichar
现在,我们来看看为什么55357,或者0xd83d,会出现在很多emoji表情中:当你只查看UTF-32字符的第一个UTF-16值时,你会得到高代理,每个代理都有1024个低代理。高代理0xd83d的范围是U+1F 400-U+1F 7 FF,它从最大的emoji块Miscellaneous Symbols and Pictographs的中间开始(U+1F 300-U+1F 5 FF),并一直延伸到Geometric Shapes Extended(U+1F 780-U+1F 7 FF)-总共包含563个表情符号,以及333个非表情符号字符。
因此,令人印象深刻的是,50%的emoji基本字符具有高代理0xd83d,但这些演绎方法仍然留下了384个emoji字符未处理,沿着至少给出了同样多的误报。

那么,如何检测一个字符是否是emoji呢?

我最近回答了一个somewhat related question with a Swift implementation,如果你愿意,你可以看看如何在this framework中检测emoji,我创建它的目的是用自定义图像替换标准emoji。
无论如何,你可以做的是从字符中提取UTF-32码位,我们将根据the specification来做:

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

在😎UITextView中键入““将以下内容写入控制台:

😎 contains values:
    - U+1f60e

按照这种逻辑,只需将character的值与emoji代码点的数据源进行比较,就可以确切地知道该字符是否是emoji。
附言
还有一些“不可见”字符,即Variation Selectorszero-width joiners,也应该被处理,所以我建议研究这些字符以了解它们的行为。

q35jwt9p

q35jwt9p3#

另一个解决方案:https://github.com/woxtu/NSString-RemoveEmoji
然后,在导入此扩展后,您可以像这样使用它:

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

希望有帮助;)

yqyhoc1h

yqyhoc1h4#

如果您不希望键盘显示emoji,可以使用YOURTEXTFIELD/YOURTEXTVIEW.keyboardType = .ASCIICapable
这将显示没有表情符号的键盘

anauzrmj

anauzrmj5#

以下是Swift中的emoji检测方法,运行正常,希望对其他人有帮助。

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

chy5wohz6#

下面是检查绘制的字符是否具有任何颜色的代码的更干净、更高效的实现。
这些方法被编写为类别/扩展方法,以使它们更易于使用。

Objective-C:

NSString+Emoji.h:

#import <Foundation/Foundation.h>

@interface NSString (Emoji)

- (BOOL)hasColor;

@end

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

示例用法:

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

Swift:

String+Emoji.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
    }
}

示例用法:

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

3duebb1j7#

Swift的String类型有一个属性isEmoji
最好检查文档中的isEmojiPresentation警告
https://developer.apple.com/documentation/swift/unicode/scalar/properties/3081577-isemoji

pcrecxhr

pcrecxhr8#

那么你可以使用这个来检测它是否只有ascii字符:

[myString canBeConvertedToEncoding:NSASCIIStringEncoding];

如果它失败了(或者有表情符号),它会说不。然后你可以做一个if else语句,不允许他们点击回车之类的。

deikduxw

deikduxw9#

Emoji字符长度为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;
}

}

相关问题