Подтвердить что ты не робот

Обнаружение, если пользователь набрал символ emoji в UITextView

У меня есть UITextView, и мне нужно определить, входит ли пользователь в символ эмози.

Я бы подумал, что просто проверка значения unicode самого нового символа будет достаточной, но с новыми emoji 2s, некоторые символы будут разбросаны по всему индексу юникода (т.е. недавно созданные логотипы авторских прав и регистрации).

Возможно, что-то связано с проверкой языка символа с значениями NSLocale или LocalizedString?

Кто-нибудь знает хорошее решение?

Спасибо!

4b9b3361

Ответ 1

На протяжении многих лет эти решения для обнаружения эмоций продолжают ломаться, когда Apple добавляет новые эмоции с новыми методами (например, эмулятор эмоций, созданный путем проклятия персонажа с дополнительным символом) и т.д.

Я, наконец, сломался и просто написал следующий метод, который работает для всех текущих emojis и должен работать для всех будущих emojis.

Решение создает 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;

}

Ответ 2

Другое решение: 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;
}

Надеюсь, что помогает;)

Ответ 3

Сначала позвольте обратиться к вашему "методу 55357" - и , почему он работает для многих символов emoji.

В Cocoa, a NSString представляет собой набор из unichar s, а unichar - это только тип соответствия для unsigned short, который совпадает с UInt16. Поскольку максимальное значение UInt16 составляет 0xffff, это исключает довольно много эмози из возможности вписаться в один unichar, так как только два из шести основных блоков Unicode, используемых для emoji, попадают под этот диапазон:

Эти блоки содержат 113 emoji, а дополнительные 66 emoji, которые могут быть представлены как одиночные unichar, могут быть найдены разбросанными по различным другим блокам. Однако эти 179 символов представляют только часть 1126 базовых символов emoji, остальная часть которых должна быть представлена ​​более чем одним unichar.

Проанализируйте свой код:

unichar unicodevalue = [text characterAtIndex:0];

Что происходит, так это то, что вы просто берете первую строку unichar, и, хотя это работает для ранее упомянутых 179 символов, она разрывается, когда вы сталкиваетесь с символом UTF-32, поскольку NSString преобразует все в кодировку UTF-16. Преобразование работает заменяя значение UTF-32 суррогатными парами, что означает, что NSString теперь содержит два unichar s.

И сейчас, мы получаем, почему число 55357 или 0xd83d появляется для многих эможи: когда вы смотрите только на первое значение UTF-16 символа UTF-32, вы получить высокий суррогат, каждый из которых имеет диапазон 1024 низких суррогатов. Диапазон для высокого суррогата 0xd83d равен U + 1F400-U + 1F7FF, который начинается в середине самого большого блока emoji, Различные символы и пиктограммы (U + 1F300-U + 1F5FF) и продолжается до Расширенные геометрические фигуры (U + 1F780-U + 1F7FF), содержащий в общей сложности 563 emoji и 333 символа неэможи в пределах этого диапазона.

Таким образом, впечатляющие 50% базовых символов emoji имеют высокий суррогат 0xd83d, но эти методы дедукции по-прежнему оставляют 384 символа emoji необработанными, а также дают ложные срабатывания, по крайней мере, так много.


Итак, как вы можете определить, является ли символ эможи или нет?

Недавно я ответил на несколько смежный вопрос с реализацией Swift, и если вы захотите, вы можете посмотреть, как emoji обнаружены в эта структура, которую я создал с целью замены стандартных emoji на пользовательские изображения.

Во всяком случае, вы можете извлечь код кода 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;
}

Ввод "😎" в UITextView записывает это на консоль:

😎 contains values:
    - U+1f60e

С этой логикой просто сравните значение character с вашим источником данных из кодов очков emoji, и вы точно узнаете, является ли этот символ эмози или нет.


P.S.

Есть несколько "невидимых" символов, а именно "Селектора вариаций" и стопоры нулевой ширины, которые также должны быть обработаны, поэтому я рекомендую изучить их, чтобы узнать, как они себя ведут.

Ответ 4

Если вы не хотите, чтобы ваша клавиатура отображала эможи, вы можете использовать YOURTEXTFIELD/YOURTEXTVIEW.keyboardType = .ASCIICapable
Это покажет клавиатуру без emoji

Ответ 5

Вот метод обнаружения смайликов в Swift. Работает нормально. Надеюсь, это поможет другим.

 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
}

Ответ 6

Ниже приведены более чистые и эффективные реализации кода, который проверяет, имеет ли нарисованный символ какой-либо цвет или нет.

Они были написаны как методы категорий/расширений, чтобы их было проще использовать.

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:

Строка + 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
}

Ответ 7

Ну, вы можете определить, есть ли у него только символы ascii:

[myString canBeConvertedToEncoding:NSASCIIStringEncoding];

Он скажет "нет", если он терпит неудачу (или имеет эможи). Затем вы можете сделать оператор if else, который не позволяет им нажимать кнопку ввода или что-то в этом роде.

Ответ 8

Длина символов Emoji равна 2 и поэтому проверьте, является ли длина строки равным 2 в методе, которая должна должна быть: ShangeTextInRange: она вызывается после каждого нажатия клавиши на клавиатуре

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

}