Saltar al contenido

Detectar si un usuario ha escrito un carácter emoji en UITextView

Solución:

A lo largo de los años, estas soluciones de detección de emojis se siguen rompiendo a medida que Apple agrega nuevos emojis con nuevos métodos (como emojis con tonos de piel creados al pre-maldecir a un personaje con un personaje adicional), etc.

Finalmente me rompí y escribí el siguiente método que funciona para todos los emojis actuales y debería funcionar para todos los emojis futuros.

La solución crea un UILabel con el personaje y un fondo negro. CG luego toma una instantánea de la etiqueta y yo escaneo todos los píxeles de la instantánea en busca de píxeles que no sean de color negro sólido. La razón por la que agrego el fondo negro es para evitar problemas de coloración falsa debido a la representación de subpíxeles

La solución se ejecuta MUY rápido en mi dispositivo, puedo verificar cientos de caracteres por segundo, pero debe tenerse en cuenta que esta es una solución de CoreGraphics y no debe usarse mucho como lo haría con un método de texto normal. El procesamiento de gráficos consume muchos datos, por lo que la verificación de miles de caracteres a la vez podría provocar un retraso notable.

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

}

Primero abordemos tu “Método 55357” – y por qué funciona para muchos personajes emoji.

En Cocoa, un NSString es una colección de unichararena unichar es solo un tipo de alias para unsigned short que es lo mismo que UInt16. Dado que el valor máximo de UInt16 es 0xffff, esto descarta que bastantes emoji puedan caber en uno unichar, ya que solo dos de los seis bloques Unicode principales utilizados para emoji caen dentro de este rango:

  • Símbolos varios (U + 2600 – U + 26FF)
  • Simbolos (U + 2700 – U + 27BF)

Estos bloques contienen 113 emoji y 66 emoji adicionales que se pueden representar como un solo unichar se pueden encontrar repartidos por varios otros bloques. Sin embargo, estos 179 caracteres solo representan una fracción de los 1126 caracteres básicos de emoji, el resto de los cuales debe estar representado por más de uno. unichar.

Analicemos tu código:

unichar unicodevalue = [text characterAtIndex:0];

Lo que sucede es que simplemente estás tomando el primer unichar de la cadena, y aunque esto funciona para los 179 caracteres mencionados anteriormente, se rompe cuando se encuentra con un carácter UTF-32, ya que NSString convierte todo en codificación UTF-16. La conversión funciona sustituyendo el valor UTF-32 con pares sustitutos, lo que significa que el NSString ahora contiene dos unichars.

Y ahora estamos llegando a por qué el número 55357, o 0xd83d, aparece para muchos emoji: cuando solo mira el primer valor UTF-16 de un carácter UTF-32, obtiene el sustituto alto, cada uno de los cuales tiene un intervalo de 1024 sustitutos bajos. El rango para el alto sustituto 0xd83d es U + 1F400 – U + 1F7FF, que comienza en el medio del bloque de emoji más grande, Símbolos y pictogramas varios (U + 1F300 – U + 1F5FF), y continúa hasta Formas geométricas extendidas (U + 1F780 – U + 1F7FF): contiene un total de 563 emoji y 333 caracteres que no son emoji dentro de este rango.

Entonces, un impresionante 50% de los personajes base de emoji tienen el alto sustituto 0xd83d, pero estos métodos de deducción aún dejan 384 caracteres emoji sin controlar, además de dar falsos positivos para al menos la misma cantidad.


Entonces, ¿cómo se puede detectar si un personaje es un emoji o no?

Recientemente respondí una pregunta algo relacionada con una implementación de Swift y, si lo desea, puede ver cómo se detectan los emoji en este marco, que creé con el propósito de reemplazar los emoji estándar con imágenes personalizadas.

De todos modos, lo que puede hacer es extraer el punto de código UTF-32 de los caracteres, lo que haremos de acuerdo con la especificación:

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

Al escribir “” en el UITextView escribe esto en la consola:

 contains values:
    - U+1f60e

Con esa lógica, simplemente compare el valor de character a su fuente de datos de puntos de código emoji, y sabrá exactamente si el personaje es un emoji o no.


PD

Hay algunos “invisible” personajes, a saber, selectores de variación y ensambladores de ancho cero, que también deben manejarse, por lo que recomiendo estudiarlos para aprender cómo se comportan.

Otra solución: https://github.com/woxtu/NSString-RemoveEmoji

Luego, después de importar esta extensión, puede usarla así:

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

Espero que ayude 😉

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *