ios - para - ΒΏAverigua si Character in String es emoji?
emojis ios descargar (13)
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.
Prueba futura: comprueba manualmente los píxeles del personaje; las otras soluciones se romperán (y se han 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 mediante la maldición previa de un personaje con un personaje adicional), etc.
Finalmente me quebré 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. CG toma una instantánea de la etiqueta y escaneo todos los píxeles de la instantánea en busca de píxeles no negros sólidos. La razón por la que agrego el fondo negro es para evitar problemas de colores falsos 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 se debe tener en cuenta que esta es una solución CoreGraphics y no se debe usar mucho como lo haría con un método de texto normal. El procesamiento de gráficos requiere muchos datos, por lo que verificar miles de caracteres a la vez podría generar 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;
}
Para Swift 5.0:
Cuentas para:
- No emoji emoji (por ejemplo, caracteres ASCII)
- Unión de ancho cero (cuenta π¨π©π§π¦ como un emoji, no 4)
- Modificadores (p. Ej., Tono de piel π½)
- Selectores de variación
Todo cierto:
"π".isPureEmojiString(withMinLength: 1, max: 1) // 1 scalar
"ππΎ".isPureEmojiString(withMinLength: 1, max: 1) // 2 scalars (emoji modifier)
"βοΈ".isPureEmojiString(withMinLength: 1, max: 1) // 2 scalars (variation selector)
"π΅π·".isPureEmojiString(withMinLength: 1, max: 1) // 2 scalars (2x regional indicators)
"π§βοΈ".isPureEmojiString(withMinLength: 1, max: 1) // 4 scalars (ZWJ + β + variation)
"π¨π©π¦π¦".isPureEmojiString(withMinLength: 1, max: 1) // 7 scalars (ZW joiners)
extension String {
func isPureEmojiString(withMinLength min: Int, max: Int) -> Bool {
if count < min || count > max {
return false
}
return isPureEmojiString()
}
func isPureEmojiString() -> Bool {
for scalar in unicodeScalars {
let prop = scalar.properties
if prop.isJoinControl || prop.isVariationSelector || prop.isEmojiModifier {
continue
}
else if scalar.properties.isEmoji == false || scalar.isASCII == true {
return false
}
}
return true
}
}
Swift 5.0
… Introdujo una nueva forma de verificar exactamente esto!
Tienes que romper tu
String
en sus
Scalars
.
¡Cada
Scalar
tiene un valor de
Property
que admite el valor
isEmoji
!
En realidad, incluso puede verificar si el Scalar es un modificador Emoji o más. Consulte la documentación de Apple: Property
Es posible que desee considerar buscar
isEmojiPresentation
lugar de
isEmoji
, porque Apple establece lo siguiente para
isEmoji
:
Esta propiedad es verdadera 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 es seguido por U + FE0F VARIATION SELECTOR-16. Esto incluye algunos escalares que generalmente 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 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)
}
}
Con Swift 5 ahora puede inspeccionar las propiedades unicode de cada personaje en su cadena.
Esto nos da la conveniente variable
isEmoji
en cada letra.
El problema es que
isEmoji
devolverá verdadero para cualquier personaje que se pueda convertir en un emoji de 2 bytes, como 0-9.
Podemos ver 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 a prueba de futuro que las soluciones de expresiones regulares que se ofrecen aquí.
if string.isIncludingEmoji {
}
Dándonos
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)
}
}
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 contra los rangos conocidos de emoji y dingbats, de esta 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
}
}
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
}
}
}
Me topé con 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 ππΏ consta de 2 escalares unicode:
- El emoji normal: π
- Un modificador de tono de piel: πΏ
Entonces, al representar los caracteres, los glifos resultantes realmente importan.
Lo que estaba buscando era una forma de detectar si una cadena es exactamente y solo un emoji. Por lo tanto, podría hacerlo más grande que el texto normal (como lo hace Messages en iOS10 y WhatsApp lo hace hoy en día). Como se describió anteriormente, el recuento de caracteres es realmente inútil. (El ''personaje de pegamento'' tampoco se considera un emoji).
Lo que puede hacer es usar CoreText para ayudarlo a dividir 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 con el siguiente resultado:
import Foundation
extension UnicodeScalar {
/// Note: This method is part of Swift 5, so you can omit this.
/// See: https://developer.apple.com/documentation/swift/unicode/scalar
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
0xE0020...0xE007F, // Tags
0xFE00...0xFE0F, // Variation Selectors
0x1F900...0x1F9FF, // Supplemental Symbols and Pictographs
0x1F018...0x1F270, // Various asian characters
0x238C...0x2454, // Misc items
0x20D0...0x20FF: // Combining Diacritical Marks for Symbols
return true
default: return false
}
}
var isZeroWidthJoiner: Bool {
return value == 8205
}
}
extension String {
// Not needed anymore in swift 4.2 and later, using `.count` will give you the correct result
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 que te 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
"π¨π©π§π§".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
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 esta manera:
"hey".containsEmoji() //false
"Hello World π".containsEmoji() //true
"Hello World π".containsOnlyEmojis() //false
"π".containsEmoji() //true
"π".containsOnlyEmojis() //true
Puede usar este example 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 cadena:
let example: NSString = "stringπ¨π¨π§π§withπemojisβπΏ" //string with emojis
let containsEmoji: Bool = example.emo_containsEmoji()
print(containsEmoji)
// Output: ["true"]
Swift 3 Nota:
Parece que el método
cnui_containsEmojiCharacters
se ha 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 .
Tuve el mismo problema y terminé haciendo una
String
y extensiones de
Character
.
El código es demasiado largo para publicarlo, ya que en realidad enumera todos los emojis (de la lista oficial Unicode v5.0) en un conjunto de
CharacterSet
. Puede 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 instancia de
String
representa o no un único personaje Emoji conocido
print("".isEmoji) // false
print("π".isEmoji) // true
print("ππ".isEmoji) // false (String is not a single Emoji)
var contiene Emoji: Bool {get}
Si la instancia de
String
contiene o no un personaje Emoji conocido
print("".containsEmoji) // false
print("π".containsEmoji) // true
print("ππ".containsEmoji) // true
var unicodeName: String {get}
Aplica un
kCFStringTransformToUnicodeName
-
CFStringTransform
en una copia de la cadena
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
kCFStringTransformToUnicodeName
-
CFStringTransform
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 del
Character
representa o no un personaje Emoji conocido
print("".isEmoji) // false
print("π".isEmoji) // true
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.