Tengo curiosidad: ¿por qué necesitas esa información?
Martin R
@EricD .: Hay muchos caracteres Unicode que toman más de un punto de código UTF-8 (por ejemplo, "€" = E2 82 AC) o más de un punto de código UTF-16 (por ejemplo, "𝄞" = D834 DD1E).
Las cadenas tienen su indexación, que es la forma preferida de usarlas. Para obtener un carácter en particular (o un grupo de grafemas más bien), puede: let character = string[string.index(after: string.startIndex)]o let secondCharacter = string[string.index(string.startIndex, offsetBy: 1)]
Paul B
Respuestas:
228
Lo que encontré es la diferencia entre caracteres, escalares Unicode y glifos.
Por ejemplo, el glifo 👨👨👧👧 consta de 7 escalares Unicode:
Entonces, al renderizar los caracteres, los glifos resultantes realmente importan.
Swift 5.0 y superior facilita mucho este proceso y elimina algunas conjeturas que teníamos que hacer. Unicode.ScalarEl nuevo Propertytipo ayuda a determinar a qué nos enfrentamos. Sin embargo, esas propiedades solo tienen sentido cuando se verifican los otros escalares dentro del glifo. Es por eso que agregaremos algunos métodos convenientes a la clase Character para ayudarnos.
Esta es, con mucho, la mejor y más correcta respuesta aquí. ¡Gracias! Una pequeña nota, sus ejemplos no coinciden con el código (cambió el nombre de containsOnlyEmoki a containsEmoji en el fragmento; supongo que porque es más correcto, en mis pruebas devolvió verdadero para cadenas con caracteres mixtos).
Tim Bull
3
Mi mal, cambié un código, supongo que lo arruiné. Actualicé el ejemplo
Kevin R
2
@Andrew: Claro, agregué otro método al ejemplo para demostrar esto :).
Kevin R
2
@ Andrew, aquí es donde se pone realmente complicado. Agregué un ejemplo de cómo hacer eso. El problema es que supuse saber cómo CoreText representará los glifos simplemente verificando los caracteres. Si alguien tiene sugerencias para un método más limpio, hágamelo saber.
Kevin R
3
@Andrew Gracias por señalar eso, cambié la forma de containsOnlyEmojiverificaciones. También actualicé el ejemplo a Swift 3.0.
Kevin R
48
La forma 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 con rangos conocidos de emoji y dingbats, así:
extensionString{
var containsEmoji: Bool {
for scalar in unicodeScalars {
switch scalar.value {
case0x1F600...0x1F64F, // Emoticons0x1F300...0x1F5FF, // Misc Symbols and Pictographs0x1F680...0x1F6FF, // Transport and Map0x2600...0x26FF, // Misc symbols0x2700...0x27BF, // Dingbats0xFE00...0xFE0F, // Variation Selectors0x1F900...0x1F9FF, // Supplemental Symbols and Pictographs0x1F1E6...0x1F1FF: // Flagsreturntruedefault:
continue
}
}
returnfalse
}
}
Un ejemplo de código como este es mucho mejor que sugerir incluir una dependencia de biblioteca de terceros. La respuesta de Shardul es un consejo imprudente a seguir: escriba siempre su propio código.
thefaj
Esto es genial, gracias por comentar a qué se refieren los casos
Shawn Throop
1
Como tanto su código, lo implementé en una respuesta aquí . Una cosa que noté es que faltan algunos emoji, tal vez porque no son parte de las categorías que enumeraste, por ejemplo, esta: Emoji de cara de robot 🤖
Cue
1
@Tel Supongo que sería el rango 0x1F900...0x1F9FF(según Wikipedia). No estoy seguro de que todo el rango deba considerarse emoji.
Frizlab
8
extensionString{
funccontainsEmoji() -> Bool {
for scalar in unicodeScalars {
switch scalar.value {
case0x3030, 0x00AE, 0x00A9,// Special Characters0x1D000...0x1F77F, // Emoticons0x2100...0x27BF, // Misc symbols and Dingbats0xFE00...0xFE0F, // Variation Selectors0x1F900...0x1F9FF: // Supplemental Symbols and Pictographsreturntruedefault:
continue
}
}
returnfalse
}
}
Es posible que desee considerar la verificación en isEmojiPresentationlugar de isEmoji, porque Apple establece lo siguiente para isEmoji:
Esta propiedad es válida para los escalares que se representan como emoji de forma predeterminada y también para los escalares que tienen una representación de emoji no predeterminada cuando son seguidos por U + FE0F VARIATION SELECTOR-16. Esto incluye algunos escalares que normalmente no se consideran emoji.
De esta manera, los Emoji se dividen en todos los modificadores, pero es mucho más sencillo de manejar. Y como Swift ahora cuenta los Emoji con modificadores (por ejemplo: 👨👩👧👦, 👨🏻💻, 🏴) como 1, puedes hacer todo tipo de cosas.
var string = "🤓 test"for scalar in string.unicodeScalars {
let isEmoji = scalar.properties.isEmoji
print("\(scalar.description) \(isEmoji)"))
}
// 🤓 true// false// t false// e false// s false// t false
NSHipster señala una forma interesante de obtener todos los Emoji:
import Foundation
var emoji = CharacterSet()
for codePoint in0x0000...0x1F0000 {
guardlet scalarValue = Unicode.Scalar(codePoint) else {
continue
}
// Implemented in Swift 5 (SE-0221)// https://github.com/apple/swift-evolution/blob/master/proposals/0221-character-properties.mdif scalarValue.properties.isEmoji {
emoji.insert(scalarValue)
}
}
Gran respuesta, gracias. Vale la pena mencionar que su min sdk debe ser 10.2 para usar esta parte de Swift 5. Además, para verificar si una cadena solo estaba compuesta por emojis, tuve que verificar si tenía una de estas propiedades:scalar.properties.isEmoji scalar.properties.isEmojiPresentation scalar.properties.isEmojiModifier scalar.properties.isEmojiModifierBase scalar.properties.isJoinControl scalar.properties.isVariationSelector
Un Springham
6
Cuidado, los números enteros del 0 al 9 se consideran emojis. Así "6".unicodeScalars.first!.properties.isEmojise evaluará comotrue
Miniroo
6
Con Swift 5 ahora puede inspeccionar las propiedades Unicode de cada carácter en su cadena. Esto nos da la isEmojivariable conveniente en cada letra. El problema es isEmojique devolverá verdadero para cualquier carácter que se pueda convertir en un emoji de 2 bytes, como 0-9.
Podemos mirar la variable isEmojiy también verificar la presencia de un modificador de emoji para determinar si los caracteres ambiguos se mostrarán como un emoji.
Esta solución debería ser mucho más preparada para el futuro que las soluciones de expresiones regulares que se ofrecen aquí.
extensionString{
funccontainsOnlyEmojis() -> Bool {
ifcount == 0 {
returnfalse
}
for character inself {
if !character.isEmoji {
returnfalse
}
}
returntrue
}
funccontainsEmoji() -> Bool {
for character inself {
if character.isEmoji {
returntrue
}
}
returnfalse
}
}
extensionCharacter{
// An emoji can either be a 2 byte unicode character or a normal UTF8 character with an emoji modifier// appended as is the case with 3️⃣. 0x238C is the first instance of UTF16 emoji that requires no modifier.// `isEmoji` will evaluate to true for any character that can be turned into an emoji by adding a modifier// such as the digit "3". To avoid this we confirm that any character below 0x238C has an emoji modifier attachedvar isEmoji: Bool {
guardlet scalar = unicodeScalars.first else { returnfalse }
return scalar.properties.isEmoji && (scalar.value > 0x238C || unicodeScalars.count > 1)
}
}
Dándonos
"hey".containsEmoji() //false"Hello World 😎".containsEmoji() //true"Hello World 😎".containsOnlyEmojis() //false"3".containsEmoji() //false"3️⃣".containsEmoji() //true
Y lo que es más es Character("3️⃣").isEmoji // truemientrasCharacter("3").isEmoji // false
Paul B
4
Swift 3 Nota:
Parece que el cnui_containsEmojiCharactersmétodo se eliminó o se movió a una biblioteca dinámica diferente. _containsEmojiSin embargo, aún debería funcionar.
let str: NSString = "hello😊"@objcprotocolNSStringPrivate{
func_containsEmoji() -> ObjCBool
}
let strPrivate = unsafeBitCast(str, to: NSStringPrivate.self)
strPrivate._containsEmoji() // true
str.value(forKey: "_containsEmoji") // 1let swiftStr = "hello😊"
(swiftStr asAnyObject).value(forKey: "_containsEmoji") // 1
Rápido 2.x:
Recientemente descubrí una API privada en la NSStringque expone la funcionalidad para detectar si una cadena contiene un carácter Emoji:
Prueba de futuro: comprueba manualmente los píxeles del personaje; las otras soluciones se romperán (y se habrán roto) a medida que se agreguen nuevos emojis.
Nota: Esto es Objective-C (se puede convertir a Swift)
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 maldecir previamente un personaje con un personaje adicional), etc.
Finalmente 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 una UILabel con el personaje y un fondo negro. CG luego toma una instantánea de la etiqueta y yo escaneo todos los píxeles en la instantánea en busca de píxeles que no sean negros sólidos. La razón por la que agrego el fondo negro es para evitar problemas de coloración falsa debido a 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 tiene 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.font = [UIFont fontWithName:@"AppleColorEmoji" size:12.0f];//Note: Size 12 font is likely not crucial for this and the detector will probably still work at an even smaller font size, so if you needed to speed this checker up for serious performance you may test lowering this to a font size like 6.0
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;//Note: Alpha Channel not really needed, if you need to speed this up for serious performance you can refactor this pixel scanner to just RGBNSUInteger 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];//Note: I wrote this method years ago, can't remember why I check HSB instead of just checking r,g,b==0; Upon further review this step might not be needed, but I haven't tested to confirm yet.
b /= 255.0f;
if (b > 0) {
colorPixelFound = YES;
}
x++;
}
x=0;
y++;
}
return colorPixelFound;
}
¡Me gusta tu forma de pensar! ;) - ¡Fuera de la caja!
Ramon
¿Por qué nos haces esto? #apple #unicodestandard 😱🤔🤪🙈😈🤕💩
d4Rk
No he mirado esto en un tiempo pero me pregunto si tengo que convertir a UIColor y luego a hsb; parece que puedo comprobar que r, g, b todos == 0? Si alguien intenta, avíseme
Albert Renshaw
Me gusta esta solución, pero ¿no romperá con un personaje como ℹ?
Juan Carlos Ospina Gonzalez
1
@JuanCarlosOspinaGonzalez Nope, en emoji que se muestra como un cuadro azul con una i blanca. Sin embargo, trae un buen punto que UILabel debería forzar la fuente AppleColorEmoji, agregando que ahora es a prueba de fallas, aunque creo que Apple lo usará por defecto para esos de todos modos
Albert Renshaw
2
Para Swift 3.0.2, la siguiente respuesta es la más simple:
Existe una buena solución para la tarea mencionada. Pero comprobar Unicode.Scalar.Properties de escalares Unicode es bueno para un solo carácter. Y no lo suficientemente flexible para Strings.
Podemos usar expresiones regulares en su lugar , un enfoque más universal. Hay una descripción detallada de cómo funciona a continuación. Y aquí va la solución.
La solución
En Swift, puede verificar si una cadena es un solo carácter Emoji, usando una extensión con una propiedad calculada de este tipo:
extensionString{
var isSingleEmoji : Bool {
ifself.count == 1 {
let emodjiGlyphPattern = "\\p{RI}{2}|(\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})|[\\p{Emoji}&&\\p{Other_symbol}])(\\x{200D}(\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})|[\\p{Emoji}&&\\p{Other_symbol}]))*"let fullRange = NSRange(location: 0, length: self.utf16.count)
iflet regex = try? NSRegularExpression(pattern: emodjiGlyphPattern, options: .caseInsensitive) {
let regMatches = regex.matches(in: self, options: NSRegularExpression.MatchingOptions(), range: fullRange)
if regMatches.count > 0 {
// if any range found — it means, that that single character is emojireturntrue
}
}
}
returnfalse
}
}
Cómo funciona (en detalles)
Un solo Emoji (un glifo) se puede reproducir mediante varios símbolos, secuencias y combinaciones diferentes.
Especificación Unicode define varias posibles representaciones de caracteres Emoji.
Emoji de un solo carácter
Un carácter Emoji reproducido por un único escalar Unicode.
Uno debería pensar que podemos usar una propiedad adicional para verificar: “Emoji_Presentation”. Pero no funciona así. Hay un Emoji como 🏟 o 🛍, que tienen la propiedad Emoji_Presentation = false.
Para asegurarnos de que el personaje se dibuja como Emoji por defecto, debemos marcar su categoría: debe ser "Otro_símbolo".
Entonces, de hecho, la expresión regular para Emoji de un solo carácter debe definirse como:
emoji_character := \p{Emoji}&&\p{Other_symbol}
Secuencia de presentación de emoji
Un personaje, que normalmente se puede dibujar como texto o como Emoji. Su apariencia depende de un siguiente símbolo especial, un selector de presentación, que indica su tipo de presentación. \ x {FE0E} define la representación del texto. \ x {FE0F} define la representación de emoji.
La secuencia se parece mucho a la secuencia de presentación, pero tiene un escalar adicional al final: \ x {20E3}. El alcance de los posibles escalares base utilizados para ello es bastante estrecho: 0-9 # * - y eso es todo. Ejemplos: 1️⃣, 8️⃣, * ️⃣.
Unicode define la secuencia de teclas de esta manera:
Algunos emojis pueden tener una apariencia modificada, como un tono de piel. Por ejemplo, Emoji 🧑 puede ser diferente: 🧑🧑🏻🧑🏼🧑🏽🧑🏾🧑🏿. Para definir un Emoji, que se llama "Emoji_Modifier_Base" en este caso, se puede utilizar un "Emoji_Modifier" posterior.
Por ejemplo, la bandera de Ucrania 🇺🇦 de hecho se representa con dos escalares: \ u {0001F1FA \ u {0001F1E6}
Expresión regular para ello:
emoji_flag_sequence := \p{RI}{2}
Secuencia de etiquetas de emoji (ETS)
Una secuencia que utiliza la denominada base de etiquetas, que va seguida de una especificación de etiqueta personalizada compuesta por un rango de símbolos \ x {E0020} - \ x {E007E} y concluida por una marca de fin de etiqueta \ x {E007F}.
Lo extraño es que Unicode permite que la etiqueta se base en emoji_modifier_sequence o emoji_presentation_sequence en ED-14a . Pero al mismo tiempo, en las expresiones regulares proporcionadas en la misma documentación , parecen verificar la secuencia basándose en un solo carácter Emoji.
En la lista de Emojis Unicode 12.1, solo hay tres de estos Emojis definidos. Todas ellas son banderas de los países del Reino Unido: Inglaterra 🏴, Escocia 🏴 y Gales 🏴. Y todos ellos se basan en un solo personaje Emoji. Entonces, será mejor que verifiquemos solo esa secuencia.
Expresión regular:
\p{Emoji} [\x{E0020}-\x{E007E}]+ \x{E007F}
Secuencia de unión de ancho cero de Emoji (secuencia ZWJ)
Un ensamblador de ancho cero es un escalar \ x {200D}. Con su ayuda, varios personajes, que ya son Emojis por sí mismos, se pueden combinar en otros nuevos.
Por ejemplo, un emoji 👨👧👦 de “familia con padre, hijo e hija” se reproduce mediante una combinación de emojis de padre 👨, hija 👧 e hijo 👦 pegados con símbolos ZWJ.
Se permite unir elementos, que son caracteres de un solo emoji, secuencias de presentación y modificadores.
La expresión regular para dicha secuencia en general se ve así:
Tuve el mismo problema y terminé haciendo extensiones Stringy Character.
El código es demasiado largo para publicarlo, ya que en realidad enumera todos los emojis (de la lista oficial de Unicode v5.0) en un CharacterSetpuedes encontrarlo aquí:
let character = string[string.index(after: string.startIndex)]
olet secondCharacter = string[string.index(string.startIndex, offsetBy: 1)]
Respuestas:
Lo que encontré es la diferencia entre caracteres, escalares Unicode y glifos.
Por ejemplo, el glifo 👨👨👧👧 consta de 7 escalares Unicode:
Otro ejemplo, el glifo 👌🏿 consta de 2 escalares Unicode:
El último, el glifo 1️⃣ contiene tres caracteres Unicode:
1
⃣
Entonces, al renderizar los caracteres, los glifos resultantes realmente importan.
Swift 5.0 y superior facilita mucho este proceso y elimina algunas conjeturas que teníamos que hacer.
Unicode.Scalar
El nuevoProperty
tipo ayuda a determinar a qué nos enfrentamos. Sin embargo, esas propiedades solo tienen sentido cuando se verifican los otros escalares dentro del glifo. Es por eso que agregaremos algunos métodos convenientes a la clase Character para ayudarnos.Para obtener más detalles, escribí un artículo que explica cómo funciona esto .
Para Swift 5.0, te deja con el siguiente resultado:
extension Character { /// A simple emoji is one scalar and presented to the user as an Emoji var isSimpleEmoji: Bool { guard let firstScalar = unicodeScalars.first else { return false } return firstScalar.properties.isEmoji && firstScalar.value > 0x238C } /// Checks if the scalars will be merged into an emoji var isCombinedIntoEmoji: Bool { unicodeScalars.count > 1 && unicodeScalars.first?.properties.isEmoji ?? false } var isEmoji: Bool { isSimpleEmoji || isCombinedIntoEmoji } } extension String { var isSingleEmoji: Bool { count == 1 && containsEmoji } var containsEmoji: Bool { contains { $0.isEmoji } } var containsOnlyEmoji: Bool { !isEmpty && !contains { !$0.isEmoji } } var emojiString: String { emojis.map { String($0) }.reduce("", +) } var emojis: [Character] { filter { $0.isEmoji } } var emojiScalars: [UnicodeScalar] { filter { $0.isEmoji }.flatMap { $0.unicodeScalars } } }
Lo que te dará los siguientes resultados:
"A̛͚̖".containsEmoji // false "3".containsEmoji // false "A̛͚̖▶️".unicodeScalars // [65, 795, 858, 790, 9654, 65039] "A̛͚̖▶️".emojiScalars // [9654, 65039] "3️⃣".isSingleEmoji // true "3️⃣".emojiScalars // [51, 65039, 8419] "👌🏿".isSingleEmoji // true "🙎🏼♂️".isSingleEmoji // true "🇹🇩".isSingleEmoji // true "⏰".isSingleEmoji // true "🌶".isSingleEmoji // true "👨👩👧👧".isSingleEmoji // true "🏴".isSingleEmoji // true "🏴".containsOnlyEmoji // true "👨👩👧👧".containsOnlyEmoji // true "Hello 👨👩👧👧".containsOnlyEmoji // false "Hello 👨👩👧👧".containsEmoji // true "👫 Héllo 👨👩👧👧".emojiString // "👫👨👩👧👧" "👨👩👧👧".count // 1 "👫 Héllœ 👨👩👧👧".emojiScalars // [128107, 128104, 8205, 128105, 8205, 128103, 8205, 128103] "👫 Héllœ 👨👩👧👧".emojis // ["👫", "👨👩👧👧"] "👫 Héllœ 👨👩👧👧".emojis.count // 2 "👫👨👩👧👧👨👨👦".isSingleEmoji // false "👫👨👩👧👧👨👨👦".containsOnlyEmoji // true
Para versiones anteriores de Swift, consulte esta esencia que contiene mi código anterior.
fuente
containsOnlyEmoji
verificaciones. También actualicé el ejemplo a Swift 3.0.La forma 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 con rangos conocidos de emoji y dingbats, así:
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 } }
fuente
0x1F900...0x1F9FF
(según Wikipedia). No estoy seguro de que todo el rango deba considerarse emoji.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.
fuente
Swift 5.0
... introdujo una nueva forma de comprobar exactamente esto.
Tienes que romper tu
String
en suScalars
. ¡Cada unoScalar
tiene unProperty
valor que respalda elisEmoji
valor!De hecho, incluso puedes comprobar si el escalar es un modificador de Emoji o más. Consulte la documentación de Apple: https://developer.apple.com/documentation/swift/unicode/scalar/properties
Es posible que desee considerar la verificación en
isEmojiPresentation
lugar deisEmoji
, porque Apple establece lo siguiente paraisEmoji
:De esta manera, los Emoji se dividen en todos los modificadores, pero es mucho más sencillo de manejar. Y como Swift ahora cuenta los Emoji con modificadores (por ejemplo: 👨👩👧👦, 👨🏻💻, 🏴) como 1, puedes hacer todo tipo de cosas.
var string = "🤓 test" for scalar in string.unicodeScalars { let isEmoji = scalar.properties.isEmoji print("\(scalar.description) \(isEmoji)")) } // 🤓 true // false // t false // e false // s false // t false
NSHipster señala una forma interesante de obtener todos los Emoji:
import Foundation var emoji = CharacterSet() for codePoint in 0x0000...0x1F0000 { guard let scalarValue = Unicode.Scalar(codePoint) else { continue } // Implemented in Swift 5 (SE-0221) // https://github.com/apple/swift-evolution/blob/master/proposals/0221-character-properties.md if scalarValue.properties.isEmoji { emoji.insert(scalarValue) } }
fuente
scalar.properties.isEmoji scalar.properties.isEmojiPresentation scalar.properties.isEmojiModifier scalar.properties.isEmojiModifierBase scalar.properties.isJoinControl scalar.properties.isVariationSelector
"6".unicodeScalars.first!.properties.isEmoji
se evaluará comotrue
Con Swift 5 ahora puede inspeccionar las propiedades Unicode de cada carácter en su cadena. Esto nos da la
isEmoji
variable conveniente en cada letra. El problema esisEmoji
que devolverá verdadero para cualquier carácter que se pueda convertir en un emoji de 2 bytes, como 0-9.Podemos mirar la variable
isEmoji
y también verificar la presencia de un modificador de emoji para determinar si los caracteres ambiguos se mostrarán como un emoji.Esta solución debería ser mucho más preparada para el futuro que las soluciones de expresiones regulares que se ofrecen aquí.
extension String { func containsOnlyEmojis() -> Bool { if count == 0 { return false } for character in self { if !character.isEmoji { return false } } return true } func containsEmoji() -> Bool { for character in self { if character.isEmoji { return true } } return false } } extension Character { // An emoji can either be a 2 byte unicode character or a normal UTF8 character with an emoji modifier // appended as is the case with 3️⃣. 0x238C is the first instance of UTF16 emoji that requires no modifier. // `isEmoji` will evaluate to true for any character that can be turned into an emoji by adding a modifier // such as the digit "3". To avoid this we confirm that any character below 0x238C has an emoji modifier attached var isEmoji: Bool { guard let scalar = unicodeScalars.first else { return false } return scalar.properties.isEmoji && (scalar.value > 0x238C || unicodeScalars.count > 1) } }
Dándonos
"hey".containsEmoji() //false "Hello World 😎".containsEmoji() //true "Hello World 😎".containsOnlyEmojis() //false "3".containsEmoji() //false "3️⃣".containsEmoji() //true
fuente
Character("3️⃣").isEmoji // true
mientrasCharacter("3").isEmoji // false
Swift 3 Nota:
Parece que el
cnui_containsEmojiCharacters
método se eliminó o se movió a una biblioteca dinámica diferente._containsEmoji
Sin embargo, 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
Rápido 2.x:
Recientemente descubrí una API privada en la
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 cuerda Swift pura, debes lanzar la cuerda como
AnyObject
antes de usarvalueForKey
: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 .
fuente
Puede utilizar este ejemplo de código o este pod .
Para usarlo en Swift, importe la categoría en el
YourProject_Bridging_Header
#import "NSString+EMOEmoji.h"
Luego, puede verificar el rango de cada emoji en su Cadena:
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.
fuente
Prueba de futuro: comprueba manualmente los píxeles del personaje; las otras soluciones se romperán (y se habrán roto) a medida que se agreguen nuevos emojis.
Nota: Esto es Objective-C (se puede convertir a Swift)
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 maldecir previamente un personaje con un personaje adicional), etc.
Finalmente 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 una UILabel con el personaje y un fondo negro. CG luego toma una instantánea de la etiqueta y yo escaneo todos los píxeles en la instantánea en busca de píxeles que no sean negros sólidos. La razón por la que agrego el fondo negro es para evitar problemas de coloración falsa debido a 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 tiene 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.font = [UIFont fontWithName:@"AppleColorEmoji" size:12.0f];//Note: Size 12 font is likely not crucial for this and the detector will probably still work at an even smaller font size, so if you needed to speed this checker up for serious performance you may test lowering this to a font size like 6.0 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;//Note: Alpha Channel not really needed, if you need to speed this up for serious performance you can refactor this pixel scanner to just RGB 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];//Note: I wrote this method years ago, can't remember why I check HSB instead of just checking r,g,b==0; Upon further review this step might not be needed, but I haven't tested to confirm yet. b /= 255.0f; if (b > 0) { colorPixelFound = YES; } x++; } x=0; y++; } return colorPixelFound; }
fuente
AppleColorEmoji
, agregando que ahora es a prueba de fallas, aunque creo que Apple lo usará por defecto para esos de todos modosPara 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; }
fuente
La respuesta absolutamente similar a las que escribieron antes que yo, pero con un conjunto actualizado de escalares 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 } } }
fuente
Puede usar NSString-RemoveEmoji así:
if string.isIncludingEmoji { }
fuente
Existe una buena solución para la tarea mencionada. Pero comprobar Unicode.Scalar.Properties de escalares Unicode es bueno para un solo carácter. Y no lo suficientemente flexible para Strings.
Podemos usar expresiones regulares en su lugar , un enfoque más universal. Hay una descripción detallada de cómo funciona a continuación. Y aquí va la solución.
La solución
En Swift, puede verificar si una cadena es un solo carácter Emoji, usando una extensión con una propiedad calculada de este tipo:
extension String { var isSingleEmoji : Bool { if self.count == 1 { let emodjiGlyphPattern = "\\p{RI}{2}|(\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})|[\\p{Emoji}&&\\p{Other_symbol}])(\\x{200D}(\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})|[\\p{Emoji}&&\\p{Other_symbol}]))*" let fullRange = NSRange(location: 0, length: self.utf16.count) if let regex = try? NSRegularExpression(pattern: emodjiGlyphPattern, options: .caseInsensitive) { let regMatches = regex.matches(in: self, options: NSRegularExpression.MatchingOptions(), range: fullRange) if regMatches.count > 0 { // if any range found — it means, that that single character is emoji return true } } } return false } }
Cómo funciona (en detalles)
Un solo Emoji (un glifo) se puede reproducir mediante varios símbolos, secuencias y combinaciones diferentes. Especificación Unicode define varias posibles representaciones de caracteres Emoji.
Emoji de un solo carácter
Un carácter Emoji reproducido por un único escalar Unicode.
Unicode define el carácter Emoji como:
emoji_character := \p{Emoji}
Pero no significa necesariamente que ese personaje se dibujará como un Emoji. Un símbolo numérico ordinario "1" tiene la propiedad Emoji siendo verdadera, aunque aún podría dibujarse como texto. Y hay una lista de tales símbolos: #, ©, 4, etc.
Uno debería pensar que podemos usar una propiedad adicional para verificar: “Emoji_Presentation”. Pero no funciona así. Hay un Emoji como 🏟 o 🛍, que tienen la propiedad Emoji_Presentation = false.
Para asegurarnos de que el personaje se dibuja como Emoji por defecto, debemos marcar su categoría: debe ser "Otro_símbolo".
Entonces, de hecho, la expresión regular para Emoji de un solo carácter debe definirse como:
emoji_character := \p{Emoji}&&\p{Other_symbol}
Secuencia de presentación de emoji
Un personaje, que normalmente se puede dibujar como texto o como Emoji. Su apariencia depende de un siguiente símbolo especial, un selector de presentación, que indica su tipo de presentación. \ x {FE0E} define la representación del texto. \ x {FE0F} define la representación de emoji.
La lista de tales símbolos se puede encontrar [aquí] ( https://unicode.org/Public/emoji/12.1/emoji-variation-sequences.txt ).
Unicode define la secuencia de presentación de esta manera:
Secuencia de expresión regular para ello:
emoji_presentation_sequence := \p{Emoji} \x{FE0F}
Secuencia de teclas de Emoji
La secuencia se parece mucho a la secuencia de presentación, pero tiene un escalar adicional al final: \ x {20E3}. El alcance de los posibles escalares base utilizados para ello es bastante estrecho: 0-9 # * - y eso es todo. Ejemplos: 1️⃣, 8️⃣, * ️⃣.
Unicode define la secuencia de teclas de esta manera:
emoji_keycap_sequence := [0-9#*] \x{FE0F 20E3}
Expresión regular para ello:
emoji_keycap_sequence := \p{Emoji} \x{FE0F} \x{FE0F}
Secuencia del modificador de emoji
Algunos emojis pueden tener una apariencia modificada, como un tono de piel. Por ejemplo, Emoji 🧑 puede ser diferente: 🧑🧑🏻🧑🏼🧑🏽🧑🏾🧑🏿. Para definir un Emoji, que se llama "Emoji_Modifier_Base" en este caso, se puede utilizar un "Emoji_Modifier" posterior.
En general, dicha secuencia se ve así:
Para detectarlo podemos buscar una secuencia de expresión regular:
emoji_modifier_sequence := \p{Emoji} \p{EMod}
Secuencia de banderas emoji
Las banderas son emojis con su estructura particular. Cada bandera está representada con dos símbolos “Regional_Indicator”.
Unicode los define como:
Por ejemplo, la bandera de Ucrania 🇺🇦 de hecho se representa con dos escalares: \ u {0001F1FA \ u {0001F1E6}
Expresión regular para ello:
emoji_flag_sequence := \p{RI}{2}
Secuencia de etiquetas de emoji (ETS)
Una secuencia que utiliza la denominada base de etiquetas, que va seguida de una especificación de etiqueta personalizada compuesta por un rango de símbolos \ x {E0020} - \ x {E007E} y concluida por una marca de fin de etiqueta \ x {E007F}.
Unicode lo define así:
emoji_tag_sequence := tag_base tag_spec tag_end tag_base := emoji_character | emoji_modifier_sequence | emoji_presentation_sequence tag_spec := [\x{E0020}-\x{E007E}]+ tag_end := \x{E007F}
Lo extraño es que Unicode permite que la etiqueta se base en emoji_modifier_sequence o emoji_presentation_sequence en ED-14a . Pero al mismo tiempo, en las expresiones regulares proporcionadas en la misma documentación , parecen verificar la secuencia basándose en un solo carácter Emoji.
En la lista de Emojis Unicode 12.1, solo hay tres de estos Emojis definidos. Todas ellas son banderas de los países del Reino Unido: Inglaterra 🏴, Escocia 🏴 y Gales 🏴. Y todos ellos se basan en un solo personaje Emoji. Entonces, será mejor que verifiquemos solo esa secuencia.
Expresión regular:
\p{Emoji} [\x{E0020}-\x{E007E}]+ \x{E007F}
Secuencia de unión de ancho cero de Emoji (secuencia ZWJ)
Un ensamblador de ancho cero es un escalar \ x {200D}. Con su ayuda, varios personajes, que ya son Emojis por sí mismos, se pueden combinar en otros nuevos.
Por ejemplo, un emoji 👨👧👦 de “familia con padre, hijo e hija” se reproduce mediante una combinación de emojis de padre 👨, hija 👧 e hijo 👦 pegados con símbolos ZWJ.
Se permite unir elementos, que son caracteres de un solo emoji, secuencias de presentación y modificadores.
La expresión regular para dicha secuencia en general se ve así:
Expresión regular para todos ellos
Todas las representaciones de Emoji mencionadas anteriormente se pueden describir mediante una sola expresión regular:
\p{RI}{2} | ( \p{Emoji} ( \p{EMod} | \x{FE0F}\x{20E3}? | [\x{E0020}-\x{E007E}]+\x{E007F} ) | [\p{Emoji}&&\p{Other_symbol}] ) ( \x{200D} ( \p{Emoji} ( \p{EMod} | \x{FE0F}\x{20E3}? | [\x{E0020}-\x{E007E}]+\x{E007F} ) | [\p{Emoji}&&\p{Other_symbol}] ) )*
fuente
Tuve el mismo problema y terminé haciendo extensiones
String
yCharacter
.El código es demasiado largo para publicarlo, ya que en realidad enumera todos los emojis (de la lista oficial de Unicode v5.0) en un
CharacterSet
puedes encontrarlo aquí:https://github.com/piterwilson/StringEmoji
Constantes
let emojiCharacterSet: CharacterSetConjunto de caracteres que contiene todos los emoji conocidos (como se describe en la lista oficial Unicode 5.0 http://unicode.org/emoji/charts-5.0/emoji-list.html )
Cuerda
var isEmoji: Bool {get}Si la
String
instancia representa o no un único carácter emoji conocido
var contieneEmoji: Bool {get}print("".isEmoji) // false print("😁".isEmoji) // true print("😁😜".isEmoji) // false (String is not a single Emoji)
Si la
String
instancia contiene o no un carácter emoji conocido
var unicodeName: String {get}print("".containsEmoji) // false print("😁".containsEmoji) // true print("😁😜".containsEmoji) // true
Aplica un
kCFStringTransformToUnicodeName
-CFStringTransform
en una copia de la Cadena
var niceUnicodeName: String {get}print("á".unicodeName) // \N{LATIN SMALL LETTER A WITH ACUTE} print("😜".unicodeName) // "\N{FACE WITH STUCK-OUT TONGUE AND WINKING EYE}"
Devuelve el resultado de a
kCFStringTransformToUnicodeName
-CFStringTransform
con\N{
prefijos y}
sufijos eliminadosprint("á".unicodeName) // LATIN SMALL LETTER A WITH ACUTE print("😜".unicodeName) // FACE WITH STUCK-OUT TONGUE AND WINKING EYE
Personaje
var isEmoji: Bool {get}Si la
Character
instancia representa o no un carácter emoji conocidoprint("".isEmoji) // false print("😁".isEmoji) // true
fuente