Découvrez si le caractère dans la chaîne est emoji?
J'ai besoin de savoir si un caractère dans une chaîne est un emoji.
Par exemple, j'ai ce caractère:
let string = "
10 réponses
Ce que je suis tombé sur est la différence entre les caractères, les scalaires unicode et les glyphes.
Par exemple, le glyphe est constitué de 7 scalaires unicode:
- Quatre caractères emoji:
- entre chaque emoji est un caractère spécial, qui fonctionne comme la colle de caractère; voir les spécifications pour plus d'Informations
Un autre exemple, le glyphe est constitué de 2 scalaires unicode:
- les emoji réguliers:
- un modificateur de tonalité de peau:
Donc, lors du rendu des caractères, les glyphes résultants comptent vraiment.
Ce que je cherchais un moyen de détecter si une chaîne est exactement et seulement un emoji. Donc, je pourrais le rendre plus grand que le texte normal(comme les Messages sur iOS10 et WhatsApp le font de nos jours). Comme décrit ci-dessus, le nombre de caractères est vraiment inutile. (Le "caractère de colle" n'est pas non plus considéré comme un emoji).
Ce que vous pouvez faire est d'utiliser CoreText pour vous aider à décomposer la chaîne en glyphes et les compter. En outre, je déplacerais une partie de l'extension proposée par Arnold et Sebastian Lopez vers une extension séparée de UnicodeScalar
.
, Il vous laisse avec le résultat suivant:
import UIKit
extension UnicodeScalar {
var isEmoji: Bool {
switch value {
case 0x1F600...0x1F64F, // Emoticons
0x1F300...0x1F5FF, // Misc Symbols and Pictographs
0x1F680...0x1F6FF, // Transport and Map
0x1F1E6...0x1F1FF, // Regional country flags
0x2600...0x26FF, // Misc symbols
0x2700...0x27BF, // Dingbats
0xFE00...0xFE0F, // Variation Selectors
0x1F900...0x1F9FF, // Supplemental Symbols and Pictographs
127000...127600, // Various asian characters
65024...65039, // Variation selector
9100...9300, // Misc items
8400...8447: // Combining Diacritical Marks for Symbols
return true
default: return false
}
}
var isZeroWidthJoiner: Bool {
return value == 8205
}
}
extension String {
var glyphCount: Int {
let richText = NSAttributedString(string: self)
let line = CTLineCreateWithAttributedString(richText)
return CTLineGetGlyphCount(line)
}
var isSingleEmoji: Bool {
return glyphCount == 1 && containsEmoji
}
var containsEmoji: Bool {
return unicodeScalars.contains { $0.isEmoji }
}
var containsOnlyEmoji: Bool {
return !isEmpty
&& !unicodeScalars.contains(where: {
!$0.isEmoji
&& !$0.isZeroWidthJoiner
})
}
// The next tricks are mostly to demonstrate how tricky it can be to determine emoji's
// If anyone has suggestions how to improve this, please let me know
var emojiString: String {
return emojiScalars.map { String($0) }.reduce("", +)
}
var emojis: [String] {
var scalars: [[UnicodeScalar]] = []
var currentScalarSet: [UnicodeScalar] = []
var previousScalar: UnicodeScalar?
for scalar in emojiScalars {
if let prev = previousScalar, !prev.isZeroWidthJoiner && !scalar.isZeroWidthJoiner {
scalars.append(currentScalarSet)
currentScalarSet = []
}
currentScalarSet.append(scalar)
previousScalar = scalar
}
scalars.append(currentScalarSet)
return scalars.map { $0.map{ String($0) } .reduce("", +) }
}
fileprivate var emojiScalars: [UnicodeScalar] {
var chars: [UnicodeScalar] = []
var previous: UnicodeScalar?
for cur in unicodeScalars {
if let previous = previous, previous.isZeroWidthJoiner && cur.isEmoji {
chars.append(previous)
chars.append(cur)
} else if cur.isEmoji {
chars.append(cur)
}
previous = cur
}
return chars
}
}
, Qui vous donnera les résultats suivants:
"".isSingleEmoji // true
"♂️".isSingleEmoji // true
"".isSingleEmoji // true
"".containsOnlyEmoji // true
"Hello ".containsOnlyEmoji // false
"Hello ".containsEmoji // true
" Héllo ".emojiString // ""
"".glyphCount // 1
"".characters.count // 4, Will return '1' in Swift 4.2 so previous method not needed anymore
" Héllœ ".emojiScalars // [128107, 128104, 8205, 128105, 8205, 128103, 8205, 128103]
" Héllœ ".emojis // ["", ""]
"".isSingleEmoji // false
"".containsOnlyEmoji // true
"".glyphCount // 3
"".characters.count // 8, Will return '3' in Swift 4.2 so previous method not needed anymore
Le moyen le plus simple, le plus propre et le plus rapide pour y parvenir est de simplement vérifier les points de code Unicode pour chaque caractère de la chaîne par rapport aux plages emoji et dingbats connues, comme ceci:
extension String {
var containsEmoji: Bool {
for scalar in unicodeScalars {
switch scalar.value {
case 0x1F600...0x1F64F, // Emoticons
0x1F300...0x1F5FF, // Misc Symbols and Pictographs
0x1F680...0x1F6FF, // Transport and Map
0x2600...0x26FF, // Misc symbols
0x2700...0x27BF, // Dingbats
0xFE00...0xFE0F, // Variation Selectors
0x1F900...0x1F9FF, // Supplemental Symbols and Pictographs
0x1F1E6...0x1F1FF: // Flags
return true
default:
continue
}
}
return false
}
}
extension String {
func containsEmoji() -> Bool {
for scalar in unicodeScalars {
switch scalar.value {
case 0x3030, 0x00AE, 0x00A9,// Special Characters
0x1D000...0x1F77F, // Emoticons
0x2100...0x27BF, // Misc symbols and Dingbats
0xFE00...0xFE0F, // Variation Selectors
0x1F900...0x1F9FF: // Supplemental Symbols and Pictographs
return true
default:
continue
}
}
return false
}
}
C'est mon correctif, avec des plages mises à jour.
Swift 3 Remarque:
Il semble que la méthode cnui_containsEmojiCharacters
ait été supprimée ou déplacée vers une bibliothèque dynamique différente. _containsEmoji
devrait quand même fonctionner.
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:
J'ai récemment découvert une API privée sur {[7] } qui expose des fonctionnalités pour détecter si une chaîne contient un caractère Emoji:
let str: NSString = "hello"
Avec un protocole objc et unsafeBitCast
:
@objc protocol NSStringPrivate {
func cnui_containsEmojiCharacters() -> ObjCBool
func _containsEmoji() -> ObjCBool
}
let strPrivate = unsafeBitCast(str, NSStringPrivate.self)
strPrivate.cnui_containsEmojiCharacters() // true
strPrivate._containsEmoji() // true
Avec valueForKey
:
str.valueForKey("cnui_containsEmojiCharacters") // 1
str.valueForKey("_containsEmoji") // 1
Avec une Chaîne Swift pure, vous devez lancer la chaîne comme AnyObject
avant d'utiliser valueForKey
:
let str = "hello"
(str as AnyObject).valueForKey("cnui_containsEmojiCharacters") // 1
(str as AnyObject).valueForKey("_containsEmoji") // 1
Méthodes trouvées dans le fichier D'en-têteNSString .
Vous pouvez utiliser ce code exemple, ou ce pod.
Pour l'utiliser dans Swift, importez la catégorie dans YourProject_Bridging_Header
#import "NSString+EMOEmoji.h"
Ensuite, vous pouvez vérifier la plage pour chaque emoji dans votre chaîne:
let example: NSString = "stringwithemojis" //string with emojis
let containsEmoji: Bool = example.emo_containsEmoji()
print(containsEmoji)
// Output: ["true"]
J'ai créé un petit exemple de projet avec le code ci-dessus.
Pour Swift 3.0.2, la réponse suivante est la plus 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;
}
Vous pouvez utiliser NSString-RemoveEmoji comme ceci:
if string.isIncludingEmoji {
}
! Objective-C (peut être converti en Swift)
Au fil des ans, ces solutions de détection d'emoji continuent de se briser car Apple ajoute de nouveaux emojis avec de nouvelles méthodes (comme des emojis à la peau tonique construits en pré-maudissant un personnage avec un personnage supplémentaire), etc.
Je suis finalement tombé en panne et j'ai juste écrit la méthode suivante qui fonctionne pour tous les emojis actuels et devrait fonctionner pour tous les emojis futurs.
La solution crée un UILabel avec le caractère et un fond noir. CG prend alors un aperçu de l'étiquette et je scanne tous les pixels dans l'instantané pour tout non solide-pixels noirs. La raison pour laquelle j'ajoute le fond noir est d'éviter les problèmes de fausse coloration dus au Rendu sous-pixel
La solution fonctionne très vite sur mon appareil, je peux vérifier des centaines de caractères par seconde, mais il convient de noter qu'il s'agit d'une solution CoreGraphics et ne devrait pas être utilisée lourdement comme vous le Pourriez avec une méthode de texte régulière. Le traitement graphique est lourd de données afin de vérifier des milliers de les caractères à la fois pourraient entraîner un retard notable.
-(BOOL)isEmoji:(NSString *)character {
UILabel *characterRender = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 1, 1)];
characterRender.text = character;
characterRender.backgroundColor = [UIColor blackColor];//needed to remove subpixel rendering colors
[characterRender sizeToFit];
CGRect rect = [characterRender bounds];
UIGraphicsBeginImageContextWithOptions(rect.size,YES,0.0f);
CGContextRef contextSnap = UIGraphicsGetCurrentContext();
[characterRender.layer renderInContext:contextSnap];
UIImage *capturedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGImageRef imageRef = [capturedImage CGImage];
NSUInteger width = CGImageGetWidth(imageRef);
NSUInteger height = CGImageGetHeight(imageRef);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
unsigned char *rawData = (unsigned char*) calloc(height * width * 4, sizeof(unsigned char));
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(rawData, width, height,
bitsPerComponent, bytesPerRow, colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGContextRelease(context);
BOOL colorPixelFound = NO;
int x = 0;
int y = 0;
while (y < height && !colorPixelFound) {
while (x < width && !colorPixelFound) {
NSUInteger byteIndex = (bytesPerRow * y) + x * bytesPerPixel;
CGFloat red = (CGFloat)rawData[byteIndex];
CGFloat green = (CGFloat)rawData[byteIndex+1];
CGFloat blue = (CGFloat)rawData[byteIndex+2];
CGFloat h, s, b, a;
UIColor *c = [UIColor colorWithRed:red green:green blue:blue alpha:1.0f];
[c getHue:&h saturation:&s brightness:&b alpha:&a];
b /= 255.0f;
if (b > 0) {
colorPixelFound = YES;
}
x++;
}
x=0;
y++;
}
return colorPixelFound;
}
La réponse absolument similaire à ceux qui ont écrit avant moi, mais avec un ensemble mis à jour de scalaires 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
}
}
}
J'ai eu le même problème et j'ai fini par faire des extensions String
et Character
.
Le code est trop long à publier car il répertorie en fait tous les emojis (de la liste officielle unicode v5. 0) dans un CharacterSet
vous pouvez le trouver ici:
Https://github.com/piterwilson/StringEmoji
Constantes
laissez emojiCharacterSet: CharacterSetJeu de caractères contenant tous les emoji connus (comme décrit dans la liste officielle Unicode 5.0 http://unicode.org/emoji/charts-5.0/emoji-list.html)
Chaîne
var isEmoji: Bool { get }Indique si l'instance String
représente ou non un caractère Emoji unique connu
print("".isEmoji) // false
print("".isEmoji) // true
print("".isEmoji) // false (String is not a single Emoji)
var containsEmoji: Bool { get }
Indique si l'instance String
contient ou non un caractère Emoji connu
print("".containsEmoji) // false
print("".containsEmoji) // true
print("".containsEmoji) // true
var unicodeName: chaîne { get }
Applique une kCFStringTransformToUnicodeName
- CFStringTransform
sur une copie de la Chaîne
print("á".unicodeName) // \N{LATIN SMALL LETTER A WITH ACUTE}
print("".unicodeName) // "\N{FACE WITH STUCK-OUT TONGUE AND WINKING EYE}"
var niceUnicodeName: chaîne { get }
Renvoie le résultat d'une kCFStringTransformToUnicodeName
- CFStringTransform
avec \N{
préfixes et }
suffixes retiré
print("á".unicodeName) // LATIN SMALL LETTER A WITH ACUTE
print("".unicodeName) // FACE WITH STUCK-OUT TONGUE AND WINKING EYE
{[25] Caractère}
var isEmoji: Bool { get }
Indique si l'instance Character
représente ou non un caractère Emoji connu
print("".isEmoji) // false
print("".isEmoji) // true