Estou curioso: por que você precisa dessa informação?
Martin R de
@EricD .: Existem muitos caracteres Unicode que levam mais de um ponto de código UTF-8 (por exemplo, "€" = E2 82 AC) ou mais de um ponto de código UTF-16 (por exemplo, "𝄞" = D834 DD1E).
Strings têm sua indexação, que é a forma preferida de usá-los. Para obter um caractere específico (ou agrupamento de grafemas), você pode: let character = string[string.index(after: string.startIndex)]ou let secondCharacter = string[string.index(string.startIndex, offsetBy: 1)]
Paul B
Respostas:
239
O que descobri é a diferença entre caracteres, escalares Unicode e glifos.
Por exemplo, o glifo 👨👨👧👧 consiste em 7 escalares Unicode:
Portanto, ao renderizar os caracteres, os glifos resultantes são realmente importantes.
O Swift 5.0 e superior tornam esse processo muito mais fácil e elimina algumas suposições que precisávamos fazer. Unicode.Scalaré novoProperty tipo de ajuda é determinar com o que estamos lidando. No entanto, essas propriedades só fazem sentido ao verificar os outros escalares dentro do glifo. É por isso que adicionaremos alguns métodos de conveniência à classe Character para nos ajudar.
Esta é de longe a melhor e mais correta resposta aqui. Obrigado! Uma pequena observação, seus exemplos não correspondem ao código (você renomeou containsOnlyEmoki para containsEmoji no snippet - presumo porque é mais correto, em meu teste retornou true para strings com caracteres mistos).
Tim Bull
3
Meu mal, mudei algum código, acho que errei. Eu atualizei o exemplo
Kevin R
2
@Andrew: Claro, adicionei outro método ao exemplo para demonstrar isso :).
Kevin R
2
@Andrew é aqui que fica tudo muito complicado. Eu adicionei um exemplo de como fazer isso. O problema é que assumi saber como o CoreText renderizará os glifos simplesmente verificando os caracteres. Se alguém tiver sugestões para um método mais limpo, por favor me avise.
Kevin R
3
@Andrew Obrigado por apontar isso, mudei a forma de containsOnlyEmojiverificação. Eu também atualizei o exemplo para Swift 3.0.
Kevin R
51
A maneira mais simples, limpa e rápida de fazer isso é simplesmente verificar os pontos de código Unicode para cada caractere na string em relação aos intervalos de emoji e dingbats conhecidos, como:
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
}
}
Um exemplo de código como este é muito melhor do que sugerir incluir uma dependência de biblioteca de terceiros. A resposta de Shardul é um conselho imprudente a seguir - sempre escreva seu próprio código.
thefaj
Isso é ótimo, obrigado por comentar a que se referem os casos
Shawn Throop
2
Gostei muito do seu código, eu o implementei em uma resposta aqui . Uma coisa que notei é que faltam alguns emojis, talvez porque eles não fazem parte das categorias que você listou, por exemplo este: Emoji de cara de robô 🤖
Dica
1
@Tel eu acho que seria o intervalo 0x1F900...0x1F9FF(por Wikipedia). Não tenho certeza se todo o intervalo deve ser considerado emoji.
Frizlab de
11
Swift 5.0
… Introduziu uma nova forma de verificar exatamente isso!
Você tem que quebrar o Stringseu Scalars. Cada Scalarum tem um Propertyvalor que suporta o isEmojivalor!
Você pode querer considerar verificar em isEmojiPresentationvez de isEmoji, porque a Apple afirma o seguinte para isEmoji:
Esta propriedade é verdadeira para escalares que são renderizados como emoji por padrão e também para escalares que têm uma renderização de emoji não padrão quando seguido por U + FE0F VARIATION SELECTOR-16. Isso inclui alguns escalares que normalmente não são considerados emoji.
Dessa forma, na verdade, os Emojis são divididos em todos os modificadores, mas é mais simples de manusear. E como o Swift agora conta os Emoji com modificadores (por exemplo: 👨👩👧👦, 👨🏻💻, 🏴) como 1, você pode fazer todo o tipo de coisas.
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 aponta uma maneira interessante de obter todos os emojis:
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)
}
}
Ótima resposta, obrigado. Vale a pena mencionar que seu min sdk deve ser 10,2 para usar esta parte do Swift 5. Também para verificar se uma string era composta apenas de emojis, tive que verificar se ela tinha uma das seguintes propriedades:scalar.properties.isEmoji scalar.properties.isEmojiPresentation scalar.properties.isEmojiModifier scalar.properties.isEmojiModifierBase scalar.properties.isJoinControl scalar.properties.isVariationSelector
A Springham
6
Cuidado, números inteiros de 0 a 9 são considerados emojis. Então "6".unicodeScalars.first!.properties.isEmojivai avaliar comotrue
Miniroo
1
Existem outros caracteres semelhantes #e *que também retornarão verdadeiro para o isEmojicheque. isEmojiPresentationparece funcionar melhor, pelo menos ele retorna falsepara 0...9, #, *e qualquer outro símbolo que eu poderia tentar em um teclado Inglês-US. Alguém tem mais experiência com ele e sabe se ele é confiável para validação de entrada?
Jan
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
}
}
Esta é a minha correção, com intervalos atualizados.
Com o Swift 5 agora você pode inspecionar as propriedades unicode de cada caractere em sua string. Isso nos dá a isEmojivariável conveniente em cada letra. O problema é isEmojique retornará verdadeiro para qualquer caractere que possa ser convertido em um emoji de 2 bytes, como 0-9.
Podemos olhar para a variável isEmojie também verificar a presença de um modificador de emoji para determinar se os caracteres ambíguos serão exibidos como um emoji.
Esta solução deve ser muito mais preparada para o futuro do que as soluções regex oferecidas aqui.
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)
}
}
Dando-nos
"hey".containsEmoji() //false"Hello World 😎".containsEmoji() //true"Hello World 😎".containsOnlyEmojis() //false"3".containsEmoji() //false"3️⃣".containsEmoji() //true
E o que é mais é Character("3️⃣").isEmoji // trueenquantoCharacter("3").isEmoji // false
Paul B
4
Swift 3 Nota:
Parece que o cnui_containsEmojiCharactersmétodo foi removido ou movido para uma biblioteca dinâmica diferente. _containsEmojiainda deve funcionar embora.
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
Swift 2.x:
Recentemente, descobri uma API privada na NSStringqual expõe a funcionalidade para detectar se uma string contém um caractere Emoji:
Prova Futura: Verifique manualmente os pixels do personagem; as outras soluções irão falhar (e quebraram) conforme novos emojis são adicionados.
Nota: Este é Objective-C (pode ser convertido para Swift)
Com o passar dos anos, essas soluções de detecção de emojis continuam surgindo à medida que a Apple adiciona novos emojis com novos métodos (como emojis em tons de pele criados por pré-amaldiçoar um personagem com um personagem adicional), etc.
Eu finalmente desisti e apenas escrevi o seguinte método que funciona para todos os emojis atuais e deve funcionar para todos os emojis futuros.
A solução cria um UILabel com o personagem e um fundo preto. CG então tira um instantâneo do rótulo e eu examino todos os pixels do instantâneo em busca de pixels não pretos sólidos. A razão pela qual adicionei o fundo preto é para evitar problemas de cores falsas devido à renderização de subpixel
A solução roda MUITO rápido no meu dispositivo, posso verificar centenas de caracteres por segundo, mas deve-se notar que esta é uma solução CoreGraphics e não deve ser usada pesadamente como você faria com um método de texto normal. O processamento de gráficos exige muitos dados, portanto, verificar milhares de caracteres de uma vez pode resultar em um atraso perceptível.
-(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;
}
Por que você está fazendo isso conosco? #apple #unicodestandard 😱🤔🤪🙈😈🤕💩
d4Rk
Faz um tempo que não vejo isso, mas me pergunto se preciso converter para UIColor e depois para hsb; parece que posso apenas verificar se r, g, b all == 0? Se alguém tentar, me avise
Albert Renshaw
eu gosto dessa solução, mas ela não vai quebrar com um personagem como ℹ?
Juan Carlos Ospina Gonzalez
1
@JuanCarlosOspinaGonzalez Não, em emoji que é renderizado como uma caixa azul com um i branco. Ele traz um bom ponto, porém, que o UILabel deve forçar a fonte a ser AppleColorEmoji, acrescentando que agora como um fail safe, embora eu ache que a Apple irá padronizá-la para essas de qualquer maneira
Albert Renshaw
2
Para o Swift 3.0.2, a seguinte resposta é a mais simples:
Existe uma boa solução para a tarefa mencionada. Mas verificar Unicode.Scalar.Properties de escalares Unicode é bom para um único caractere. E não é flexível o suficiente para Strings.
Podemos usar expressões regulares em vez - uma abordagem mais universal. Há uma descrição detalhada de como funciona abaixo. E aqui vai a solução.
A solução
No Swift, você pode verificar se uma String é um único caractere Emoji, usando uma extensão com tal propriedade computada:
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
}
}
Como funciona (em detalhes)
Um único Emoji (um glifo) pode ser reproduzido por uma série de diferentes símbolos, sequências e suas combinações.
Especificação Unicode define várias representações possíveis de caracteres Emoji.
Emoji de um único caractere
Um caractere Emoji reproduzido por um único Unicode Scalar.
Deve-se pensar que podemos usar uma propriedade adicional para verificar: “Emoji_Presentation”. Mas não funciona assim. Existe um Emoji como 🏟 ou 🛍, que tem a propriedade Emoji_Presentation = false.
Para ter certeza de que o personagem é desenhado como Emoji por padrão, devemos verificar sua categoria: deve ser “Other_symbol”.
Portanto, na verdade, a expressão regular para Emoji de um único caractere deve ser definida como:
emoji_character := \p{Emoji}&&\p{Other_symbol}
Sequência de apresentação de emoji
Um personagem, que normalmente pode ser desenhado como texto ou como Emoji. Sua aparência depende de um símbolo especial seguinte, um seletor de apresentação, que indica o tipo de apresentação. \ x {FE0E} define a representação de texto. \ x {FE0F} define a representação de emoji.
A sequência se parece muito com a sequência de apresentação, mas tem escalar adicional no final: \ x {20E3}. O escopo dos escalares de base possíveis usados para isso é bastante estreito: 0-9 # * - e isso é tudo. Exemplos: 1️⃣, 8️⃣, * ️⃣.
Alguns Emojis podem ter uma aparência modificada, como um tom de pele. Por exemplo, Emoji 🧑 pode ser diferente: 🧑🧑🏻🧑🏼🧑🏽🧑🏾🧑🏿. Para definir um Emoji, que é chamado de “Emoji_Modifier_Base” neste caso, pode-se usar um subsequente “Emoji_Modifier”.
Em geral, essa sequência tem a seguinte aparência:
Por exemplo, bandeira da Ucrânia 🇺🇦 de fato é representada por dois escalares: \ u {0001F1FA \ u {0001F1E6}
Expressão regular para isso:
emoji_flag_sequence := \p{RI}{2}
Sequência de Emoji Tag (ETS)
Uma sequência que usa uma chamada tag_base, que é seguida por uma especificação de tag personalizada composta de um intervalo de símbolos \ x {E0020} - \ x {E007E} e concluída por tag_end mark \ x {E007F}.
O estranho é que o Unicode permite que a tag seja baseada em emoji_modifier_sequence ou emoji_presentation_sequence em ED-14a . Mas, ao mesmo tempo, em expressões regulares fornecidas na mesma documentação, eles parecem verificar a sequência com base em um único caractere Emoji.
Na lista de Emojis Unicode 12.1, existem apenas três desses Emojis definidos. Todas são bandeiras dos países do Reino Unido: Inglaterra 🏴, Escócia 🏴 e País de Gales 🏴. E todos eles são baseados em um único personagem Emoji. Portanto, é melhor verificarmos apenas essa sequência.
Expressão regular:
\p{Emoji} [\x{E0020}-\x{E007E}]+ \x{E007F}
Emoji Zero-Width Joiner Sequence (sequência ZWJ)
Um joiner de largura zero é um escalar \ x {200D}. Com sua ajuda, vários personagens, que já são Emojis por si só, podem ser combinados em novos.
Por exemplo, uma “família com pai, filho e filha” Emoji 👨👧👦 é reproduzida por uma combinação de Emojis pai 👨, filha 👧 e filho 👦 colados com símbolos ZWJ.
É permitido colar elementos, que são caracteres Emoji simples, sequências de apresentação e modificadores.
A expressão regular para tal sequência em geral se parece com isto:
Tive o mesmo problema e acabei fazendo um Stringe Characterextensões.
O código é muito longo para postar, pois ele realmente lista todos os emojis (da lista unicode oficial v5.0) em um, CharacterSetvocê pode encontrá-lo aqui:
let character = string[string.index(after: string.startIndex)]
oulet secondCharacter = string[string.index(string.startIndex, offsetBy: 1)]
Respostas:
O que descobri é a diferença entre caracteres, escalares Unicode e glifos.
Por exemplo, o glifo 👨👨👧👧 consiste em 7 escalares Unicode:
Outro exemplo, o glifo 👌🏿 consiste em 2 escalares Unicode:
Por último, o glifo 1️⃣ contém três caracteres Unicode:
1
⃣
Portanto, ao renderizar os caracteres, os glifos resultantes são realmente importantes.
O Swift 5.0 e superior tornam esse processo muito mais fácil e elimina algumas suposições que precisávamos fazer.
Unicode.Scalar
é novoProperty
tipo de ajuda é determinar com o que estamos lidando. No entanto, essas propriedades só fazem sentido ao verificar os outros escalares dentro do glifo. É por isso que adicionaremos alguns métodos de conveniência à classe Character para nos ajudar.Para obter mais detalhes, escrevi um artigo explicando como isso funciona .
Para Swift 5.0, ele deixa você com o seguinte 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 } } }
O que lhe dará os seguintes 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 versões mais antigas do Swift, verifique esta essência que contém meu código antigo.
fonte
containsOnlyEmoji
verificação. Eu também atualizei o exemplo para Swift 3.0.A maneira mais simples, limpa e rápida de fazer isso é simplesmente verificar os pontos de código Unicode para cada caractere na string em relação aos intervalos de emoji e dingbats conhecidos, como:
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 } }
fonte
0x1F900...0x1F9FF
(por Wikipedia). Não tenho certeza se todo o intervalo deve ser considerado emoji.Swift 5.0
… Introduziu uma nova forma de verificar exatamente isso!
Você tem que quebrar o
String
seuScalars
. CadaScalar
um tem umProperty
valor que suporta oisEmoji
valor!Na verdade, você pode até verificar se o escalar é um modificador de Emoji ou mais. Verifique a documentação da Apple: https://developer.apple.com/documentation/swift/unicode/scalar/properties
Você pode querer considerar verificar em
isEmojiPresentation
vez deisEmoji
, porque a Apple afirma o seguinte paraisEmoji
:Dessa forma, na verdade, os Emojis são divididos em todos os modificadores, mas é mais simples de manusear. E como o Swift agora conta os Emoji com modificadores (por exemplo: 👨👩👧👦, 👨🏻💻, 🏴) como 1, você pode fazer todo o tipo de coisas.
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 aponta uma maneira interessante de obter todos os emojis:
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) } }
fonte
scalar.properties.isEmoji scalar.properties.isEmojiPresentation scalar.properties.isEmojiModifier scalar.properties.isEmojiModifierBase scalar.properties.isJoinControl scalar.properties.isVariationSelector
"6".unicodeScalars.first!.properties.isEmoji
vai avaliar comotrue
#
e*
que também retornarão verdadeiro para oisEmoji
cheque.isEmojiPresentation
parece funcionar melhor, pelo menos ele retornafalse
para0...9
,#
,*
e qualquer outro símbolo que eu poderia tentar em um teclado Inglês-US. Alguém tem mais experiência com ele e sabe se ele é confiável para validação de entrada?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 é a minha correção, com intervalos atualizados.
fonte
Com o Swift 5 agora você pode inspecionar as propriedades unicode de cada caractere em sua string. Isso nos dá a
isEmoji
variável conveniente em cada letra. O problema éisEmoji
que retornará verdadeiro para qualquer caractere que possa ser convertido em um emoji de 2 bytes, como 0-9.Podemos olhar para a variável
isEmoji
e também verificar a presença de um modificador de emoji para determinar se os caracteres ambíguos serão exibidos como um emoji.Esta solução deve ser muito mais preparada para o futuro do que as soluções regex oferecidas aqui.
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) } }
Dando-nos
"hey".containsEmoji() //false "Hello World 😎".containsEmoji() //true "Hello World 😎".containsOnlyEmojis() //false "3".containsEmoji() //false "3️⃣".containsEmoji() //true
fonte
Character("3️⃣").isEmoji // true
enquantoCharacter("3").isEmoji // false
Swift 3 Nota:
Parece que o
cnui_containsEmojiCharacters
método foi removido ou movido para uma biblioteca dinâmica diferente._containsEmoji
ainda deve funcionar embora.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:
Recentemente, descobri uma API privada na
NSString
qual expõe a funcionalidade para detectar se uma string contém um caractere Emoji:let str: NSString = "hello😊"
Com um protocolo objc e
unsafeBitCast
:@objc protocol NSStringPrivate { func cnui_containsEmojiCharacters() -> ObjCBool func _containsEmoji() -> ObjCBool } let strPrivate = unsafeBitCast(str, NSStringPrivate.self) strPrivate.cnui_containsEmojiCharacters() // true strPrivate._containsEmoji() // true
Com
valueForKey
:str.valueForKey("cnui_containsEmojiCharacters") // 1 str.valueForKey("_containsEmoji") // 1
Com uma string Swift pura, você deve lançar a string como
AnyObject
antes de usarvalueForKey
:let str = "hello😊" (str as AnyObject).valueForKey("cnui_containsEmojiCharacters") // 1 (str as AnyObject).valueForKey("_containsEmoji") // 1
Métodos encontrados no arquivo de cabeçalho NSString .
fonte
Você pode usar este exemplo de código ou este pod .
Para usá-lo no Swift, importe a categoria para o
YourProject_Bridging_Header
#import "NSString+EMOEmoji.h"
Em seguida, você pode verificar o intervalo de cada emoji em sua String:
let example: NSString = "string👨👨👧👧with😍emojis✊🏿" //string with emojis let containsEmoji: Bool = example.emo_containsEmoji() print(containsEmoji) // Output: ["true"]
Criei um pequeno projeto de exemplo com o código acima.
fonte
Prova Futura: Verifique manualmente os pixels do personagem; as outras soluções irão falhar (e quebraram) conforme novos emojis são adicionados.
Nota: Este é Objective-C (pode ser convertido para Swift)
Com o passar dos anos, essas soluções de detecção de emojis continuam surgindo à medida que a Apple adiciona novos emojis com novos métodos (como emojis em tons de pele criados por pré-amaldiçoar um personagem com um personagem adicional), etc.
Eu finalmente desisti e apenas escrevi o seguinte método que funciona para todos os emojis atuais e deve funcionar para todos os emojis futuros.
A solução cria um UILabel com o personagem e um fundo preto. CG então tira um instantâneo do rótulo e eu examino todos os pixels do instantâneo em busca de pixels não pretos sólidos. A razão pela qual adicionei o fundo preto é para evitar problemas de cores falsas devido à renderização de subpixel
A solução roda MUITO rápido no meu dispositivo, posso verificar centenas de caracteres por segundo, mas deve-se notar que esta é uma solução CoreGraphics e não deve ser usada pesadamente como você faria com um método de texto normal. O processamento de gráficos exige muitos dados, portanto, verificar milhares de caracteres de uma vez pode resultar em um atraso perceptível.
-(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; }
fonte
AppleColorEmoji
, acrescentando que agora como um fail safe, embora eu ache que a Apple irá padronizá-la para essas de qualquer maneiraPara o Swift 3.0.2, a seguinte resposta é a mais simples:
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; }
fonte
A resposta absolutamente semelhante àquelas que escrevi antes de mim, mas com um conjunto atualizado de escalares 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 } } }
fonte
Você pode usar NSString-RemoveEmoji assim:
if string.isIncludingEmoji { }
fonte
Existe uma boa solução para a tarefa mencionada. Mas verificar Unicode.Scalar.Properties de escalares Unicode é bom para um único caractere. E não é flexível o suficiente para Strings.
Podemos usar expressões regulares em vez - uma abordagem mais universal. Há uma descrição detalhada de como funciona abaixo. E aqui vai a solução.
A solução
No Swift, você pode verificar se uma String é um único caractere Emoji, usando uma extensão com tal propriedade computada:
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 } }
Como funciona (em detalhes)
Um único Emoji (um glifo) pode ser reproduzido por uma série de diferentes símbolos, sequências e suas combinações. Especificação Unicode define várias representações possíveis de caracteres Emoji.
Emoji de um único caractere
Um caractere Emoji reproduzido por um único Unicode Scalar.
O Unicode define o caractere emoji como:
emoji_character := \p{Emoji}
Mas isso não significa necessariamente que tal personagem será desenhado como um Emoji. Um símbolo numérico comum “1” tem a propriedade Emoji sendo verdadeira, embora ainda possa ser desenhado como texto. E há uma lista de tais símbolos: #, ©, 4, etc.
Deve-se pensar que podemos usar uma propriedade adicional para verificar: “Emoji_Presentation”. Mas não funciona assim. Existe um Emoji como 🏟 ou 🛍, que tem a propriedade Emoji_Presentation = false.
Para ter certeza de que o personagem é desenhado como Emoji por padrão, devemos verificar sua categoria: deve ser “Other_symbol”.
Portanto, na verdade, a expressão regular para Emoji de um único caractere deve ser definida como:
emoji_character := \p{Emoji}&&\p{Other_symbol}
Sequência de apresentação de emoji
Um personagem, que normalmente pode ser desenhado como texto ou como Emoji. Sua aparência depende de um símbolo especial seguinte, um seletor de apresentação, que indica o tipo de apresentação. \ x {FE0E} define a representação de texto. \ x {FE0F} define a representação de emoji.
A lista de tais símbolos pode ser encontrada [aqui] ( https://unicode.org/Public/emoji/12.1/emoji-variation-sequences.txt ).
O Unicode define a sequência de apresentação como esta:
Sequência de expressão regular para ele:
emoji_presentation_sequence := \p{Emoji} \x{FE0F}
Emoji Keycap Sequence
A sequência se parece muito com a sequência de apresentação, mas tem escalar adicional no final: \ x {20E3}. O escopo dos escalares de base possíveis usados para isso é bastante estreito: 0-9 # * - e isso é tudo. Exemplos: 1️⃣, 8️⃣, * ️⃣.
O Unicode define a sequência de teclas como esta:
emoji_keycap_sequence := [0-9#*] \x{FE0F 20E3}
Expressão regular para isso:
emoji_keycap_sequence := \p{Emoji} \x{FE0F} \x{FE0F}
Sequência de modificador de emoji
Alguns Emojis podem ter uma aparência modificada, como um tom de pele. Por exemplo, Emoji 🧑 pode ser diferente: 🧑🧑🏻🧑🏼🧑🏽🧑🏾🧑🏿. Para definir um Emoji, que é chamado de “Emoji_Modifier_Base” neste caso, pode-se usar um subsequente “Emoji_Modifier”.
Em geral, essa sequência tem a seguinte aparência:
Para detectá-lo, podemos pesquisar uma sequência de expressão regular:
emoji_modifier_sequence := \p{Emoji} \p{EMod}
Emoji Flag Sequence
As bandeiras são emojis com sua estrutura particular. Cada bandeira é representada por dois símbolos “Regional_Indicator”.
Unicode os define como:
Por exemplo, bandeira da Ucrânia 🇺🇦 de fato é representada por dois escalares: \ u {0001F1FA \ u {0001F1E6}
Expressão regular para isso:
emoji_flag_sequence := \p{RI}{2}
Sequência de Emoji Tag (ETS)
Uma sequência que usa uma chamada tag_base, que é seguida por uma especificação de tag personalizada composta de um intervalo de símbolos \ x {E0020} - \ x {E007E} e concluída por tag_end mark \ x {E007F}.
Unicode o define assim:
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}
O estranho é que o Unicode permite que a tag seja baseada em emoji_modifier_sequence ou emoji_presentation_sequence em ED-14a . Mas, ao mesmo tempo, em expressões regulares fornecidas na mesma documentação, eles parecem verificar a sequência com base em um único caractere Emoji.
Na lista de Emojis Unicode 12.1, existem apenas três desses Emojis definidos. Todas são bandeiras dos países do Reino Unido: Inglaterra 🏴, Escócia 🏴 e País de Gales 🏴. E todos eles são baseados em um único personagem Emoji. Portanto, é melhor verificarmos apenas essa sequência.
Expressão regular:
\p{Emoji} [\x{E0020}-\x{E007E}]+ \x{E007F}
Emoji Zero-Width Joiner Sequence (sequência ZWJ)
Um joiner de largura zero é um escalar \ x {200D}. Com sua ajuda, vários personagens, que já são Emojis por si só, podem ser combinados em novos.
Por exemplo, uma “família com pai, filho e filha” Emoji 👨👧👦 é reproduzida por uma combinação de Emojis pai 👨, filha 👧 e filho 👦 colados com símbolos ZWJ.
É permitido colar elementos, que são caracteres Emoji simples, sequências de apresentação e modificadores.
A expressão regular para tal sequência em geral se parece com isto:
Expressão regular para todos eles
Todas as representações de Emoji mencionadas acima podem ser descritas por uma única expressão 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}] ) )*
fonte
Tive o mesmo problema e acabei fazendo um
String
eCharacter
extensões.O código é muito longo para postar, pois ele realmente lista todos os emojis (da lista unicode oficial v5.0) em um,
CharacterSet
você pode encontrá-lo aqui:https://github.com/piterwilson/StringEmoji
Constantes
let emojiCharacterSet: CharacterSetConjunto de caracteres contendo todos os emojis conhecidos (conforme descrito na Lista Unicode 5.0 oficial http://unicode.org/emoji/charts-5.0/emoji-list.html )
Corda
var isEmoji: Bool {get}Se a
String
instância representa ou não um único caractere Emoji conhecido
var containsEmoji: Bool {get}print("".isEmoji) // false print("😁".isEmoji) // true print("😁😜".isEmoji) // false (String is not a single Emoji)
Se a
String
instância contém ou não um caractere Emoji conhecido
var unicodeName: String {get}print("".containsEmoji) // false print("😁".containsEmoji) // true print("😁😜".containsEmoji) // true
Aplica um
kCFStringTransformToUnicodeName
-CFStringTransform
em uma cópia da String
var niceUnicodeName: String {get}print("á".unicodeName) // \N{LATIN SMALL LETTER A WITH ACUTE} print("😜".unicodeName) // "\N{FACE WITH STUCK-OUT TONGUE AND WINKING EYE}"
Retorna o resultado de a
kCFStringTransformToUnicodeName
-CFStringTransform
com\N{
prefixos e}
sufixos removidosprint("á".unicodeName) // LATIN SMALL LETTER A WITH ACUTE print("😜".unicodeName) // FACE WITH STUCK-OUT TONGUE AND WINKING EYE
Personagem
var isEmoji: Bool {get}Se a
Character
instância representa ou não um caractere Emoji conhecidoprint("".isEmoji) // false print("😁".isEmoji) // true
fonte