Averiguar si el carácter en cadena es emoji?

Necesito averiguar si un personaje en una cadena es un emoji.

Por ejemplo, tengo este personaje:

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

Necesito averiguar si ese personaje es un emoji.

Lo que encontré es la diferencia entre caracteres, escalares unicode y glifos.

Por ejemplo, el glifo 👨👨👧👧 consta de 7 escalares unicode:

  • Cuatro personajes emoji: 👨👩👧👧
  • Entre cada emoji hay un personaje especial, que funciona como pegamento de personaje; ver las especificaciones para más información

Otro ejemplo, el glifo 👌🏿 consiste en 2 escalares unicode:

  • El emoji regular: 👌
  • Un modificador de tono de piel: 🏿

Entonces, al renderizar los personajes, los glifos resultantes realmente importan.

Lo que estaba buscando era una forma de detectar si una cadena es exactamente y solo un emoji. Así que podría renderizarlo más grande que el texto normal (como lo hace Messages en iOS10 y WhatsApp en la actualidad). Como se describió anteriormente, el recuento de caracteres no sirve de nada. (El ‘personaje pegamento’ tampoco se considera un emoji).

Lo que puedes hacer es usar CoreText para ayudarte a descomponer la cadena en glifos y contarlos. Además, movería parte de la extensión propuesta por Arnold y Sebastian Lopez a una extensión separada de UnicodeScalar .

Te deja el siguiente resultado:

 import UIKit extension UnicodeScalar { var isEmoji: Bool { switch value { case 0x1F600...0x1F64F, // Emoticons 0x1F300...0x1F5FF, // Misc Symbols and Pictographs 0x1F680...0x1F6FF, // Transport and Map 0x1F1E6...0x1F1FF, // Regional country flags 0x2600...0x26FF, // Misc symbols 0x2700...0x27BF, // Dingbats 0xFE00...0xFE0F, // Variation Selectors 0x1F900...0x1F9FF, // Supplemental Symbols and Pictographs 127000...127600, // Various asian characters 65024...65039, // Variation selector 9100...9300, // Misc items 8400...8447: // Combining Diacritical Marks for Symbols return true default: return false } } var isZeroWidthJoiner: Bool { return value == 8205 } } extension String { var glyphCount: Int { let richText = NSAttributedString(string: self) let line = CTLineCreateWithAttributedString(richText) return CTLineGetGlyphCount(line) } var isSingleEmoji: Bool { return glyphCount == 1 && containsEmoji } var containsEmoji: Bool { return unicodeScalars.contains { $0.isEmoji } } var containsOnlyEmoji: Bool { return !isEmpty && !unicodeScalars.contains(where: { !$0.isEmoji && !$0.isZeroWidthJoiner }) } // The next tricks are mostly to demonstrate how tricky it can be to determine emoji's // If anyone has suggestions how to improve this, please let me know var emojiString: String { return emojiScalars.map { String($0) }.reduce("", +) } var emojis: [String] { var scalars: [[UnicodeScalar]] = [] var currentScalarSet: [UnicodeScalar] = [] var previousScalar: UnicodeScalar? for scalar in emojiScalars { if let prev = previousScalar, !prev.isZeroWidthJoiner && !scalar.isZeroWidthJoiner { scalars.append(currentScalarSet) currentScalarSet = [] } currentScalarSet.append(scalar) previousScalar = scalar } scalars.append(currentScalarSet) return scalars.map { $0.map{ String($0) } .reduce("", +) } } fileprivate var emojiScalars: [UnicodeScalar] { var chars: [UnicodeScalar] = [] var previous: UnicodeScalar? for cur in unicodeScalars { if let previous = previous, previous.isZeroWidthJoiner && cur.isEmoji { chars.append(previous) chars.append(cur) } else if cur.isEmoji { chars.append(cur) } previous = cur } return chars } } 

Lo cual le dará los siguientes resultados:

 "👌🏿".isSingleEmoji // true "🙎🏼‍♂️".isSingleEmoji // true "👨‍👩‍👧‍👧".isSingleEmoji // true "👨‍👩‍👧‍👧".containsOnlyEmoji // true "Hello 👨‍👩‍👧‍👧".containsOnlyEmoji // false "Hello 👨‍👩‍👧‍👧".containsEmoji // true "👫 Héllo 👨‍👩‍👧‍👧".emojiString // "👫👨‍👩‍👧‍👧" "👨‍👩‍👧‍👧".glyphCount // 1 "👨‍👩‍👧‍👧".characters.count // 4, Will return '1' in Swift 4.2 so previous method not needed anymore "👫 Héllœ 👨‍👩‍👧‍👧".emojiScalars // [128107, 128104, 8205, 128105, 8205, 128103, 8205, 128103] "👫 Héllœ 👨‍👩‍👧‍👧".emojis // ["👫", "👨‍👩‍👧‍👧"] "👫👨‍👩‍👧‍👧👨‍👨‍👦".isSingleEmoji // false "👫👨‍👩‍👧‍👧👨‍👨‍👦".containsOnlyEmoji // true "👫👨‍👩‍👧‍👧👨‍👨‍👦".glyphCount // 3 "👫👨‍👩‍👧‍👧👨‍👨‍👦".characters.count // 8, Will return '3' in Swift 4.2 so previous method not needed anymore 

La manera más simple, limpia y rápida de lograr esto es simplemente verificar los puntos de código Unicode para cada carácter en la cadena contra los rangos conocidos de emoji y dingbats, de la siguiente manera:

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

Esta es mi solución, con rangos actualizados.

Swift 3 Nota:

Parece que el método cnui_containsEmojiCharacters ha sido eliminado o movido a una biblioteca dinámica diferente. _containsEmoji aún debería funcionar.

 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 

Swift 2.x:

Recientemente descubrí una API privada en NSString que expone la funcionalidad para detectar si una cadena contiene un carácter Emoji:

 let str: NSString = "hello😊" 

Con un protocolo objc y unsafeBitCast :

 @objc protocol NSStringPrivate { func cnui_containsEmojiCharacters() -> ObjCBool func _containsEmoji() -> ObjCBool } let strPrivate = unsafeBitCast(str, NSStringPrivate.self) strPrivate.cnui_containsEmojiCharacters() // true strPrivate._containsEmoji() // true 

Con valueForKey :

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

Con una cadena Swift pura, debe convertir la cadena como AnyObject antes de usar valueForKey :

 let str = "hello😊" (str as AnyObject).valueForKey("cnui_containsEmojiCharacters") // 1 (str as AnyObject).valueForKey("_containsEmoji") // 1 

Métodos encontrados en el archivo de encabezado NSString .

Puede usar este ejemplo de código o este pod .

Para usarlo en Swift, importe la categoría en YourProject_Bridging_Header

 #import "NSString+EMOEmoji.h" 

Luego puedes verificar el rango de cada emoji en tu String:

 let example: NSString = "string👨‍👨‍👧‍👧with😍emojis✊🏿" //string with emojis let containsEmoji: Bool = example.emo_containsEmoji() print(containsEmoji) // Output: ["true"] 

Creé un pequeño proyecto de ejemplo con el código anterior.

Para Swift 3.0.2, la siguiente respuesta es la más simple:

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

Puede usar NSString-RemoveEmoji de esta manera:

 if string.isIncludingEmoji { } 

! Objective-C (se puede convertir a Swift)

Con el paso de los años, estas soluciones de detección de emoji se siguen rompiendo a medida que Apple agrega nuevos emojis con nuevos métodos (como emojis de tonos de piel creados mediante el pre-insulto de un personaje con un carácter adicional), etc.

Finalmente me rompí y simplemente 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. A continuación, CG toma una instantánea de la etiqueta y escaneo todos los píxeles en la instantánea para cualquier píxel que no sea 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 en gran medida como podría hacerlo con un método de texto normal. El procesamiento de gráficos es pesado en los datos, por lo que revisar miles de caracteres a la vez podría ocasionar 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; } 

La respuesta absolutamente similar a las que escribí antes, pero con un conjunto actualizado de escalas de emoji.

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

Tuve el mismo problema y terminé haciendo extensiones de String y Character .

El código es demasiado largo para publicar, ya que en realidad enumera todos los emojis (de la lista unicode oficial v5.0) en un CharacterSet que puedes encontrar aquí:

https://github.com/piterwilson/StringEmoji

Constantes

dejar emojiCharacterSet: CharacterSet

Conjunto de caracteres que contiene todos los emojis conocidos (como se describe en la lista oficial de Unicode 5.0 http://unicode.org/emoji/charts-5.0/emoji-list.html )

Cuerda

var isEmoji: Bool {get}

Si la instancia de String representa o no un personaje de Emoji único conocido

 print("".isEmoji) // false print("😁".isEmoji) // true print("😁😜".isEmoji) // false (String is not a single Emoji) 

var containsEmoji: Bool {get}

Si la instancia de String contiene o no un personaje de Emoji conocido

 print("".containsEmoji) // false print("😁".containsEmoji) // true print("😁😜".containsEmoji) // true 

var unicodeName: String {get}

Aplica un kCFStringTransformToUnicodeNameCFStringTransform en una copia del String

 print("á".unicodeName) // \N{LATIN SMALL LETTER A WITH ACUTE} print("😜".unicodeName) // "\N{FACE WITH STUCK-OUT TONGUE AND WINKING EYE}" 

var niceUnicodeName: String {get}

Devuelve el resultado de un kCFStringTransformToUnicodeNameCFStringTransform con \N{ prefijos y } sufijos eliminados

 print("á".unicodeName) // LATIN SMALL LETTER A WITH ACUTE print("😜".unicodeName) // FACE WITH STUCK-OUT TONGUE AND WINKING EYE 

Personaje

var isEmoji: Bool {get}

Si la instancia de Character representa o no un Character conocido de Emoji

 print("".isEmoji) // false print("😁".isEmoji) // true