web-dev-qa-db-fra.com

Savoir si le personnage dans String est un emoji?

Je dois savoir si un personnage dans une chaîne est un emoji.

Par exemple, j'ai ce personnage:

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

Je dois savoir si ce personnage est un emoji.

63
Andrew

Ce que je suis tombé sur est la différence entre les caractères, les scalaires Unicode et les glyphes.

Par exemple, le glyphe ?????? ????? ???? se compose de 7 scalaires unicode:

Un autre exemple, le glyphe ???????? se compose de 2 scalaires unicode:

  • L'emoji régulier: ????
  • Un modificateur de teint de peau: ????

Ainsi, lors du rendu des caractères, les glyphes résultants importent vraiment.

Ce que je cherchais était un moyen de détecter si une chaîne est exactement et un seul emoji. Je pourrais donc le rendre plus gros que du texte normal (comme Messages le fait sur iOS10 et que WhatsApp le fait de nos jours). Comme décrit ci-dessus, le nombre de caractères est vraiment inutile. (Le "caractère de la colle" n'est pas non plus considéré comme un emoji).

Ce que vous pouvez faire, c'est utiliser CoreText pour vous aider à décomposer la chaîne en glyphes et à les compter. De plus, je déplacerais une partie de l'extension proposée par Arnold et Sebastian Lopez vers une extension séparée de UnicodeScalar.

Cela vous laisse avec le résultat suivant:

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

Ce 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
"????‍????‍????‍????".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
148
Kevin R

Le moyen le plus simple, le plus propre et le plus rapide est simplement de vérifier les points de code Unicode de chaque caractère de la chaîne par rapport aux plages emoji et dingbats connues, comme suit:

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
    }

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

Ceci est ma solution, avec des gammes mises à jour.

8
Sebastian Lopez

Swift 5.0

… Introduit une nouvelle façon de vérifier cela!

Vous devez diviser votre String en son Scalars . Chaque Scalar a une valeur Property qui prend en charge la valeur isEmoji !

En fait, vous pouvez même vérifier si le scalaire est un modificateur Emoji ou plus. Consultez la documentation d'Apple: https://developer.Apple.com/documentation/Swift/unicode/scalar/properties

Vous pouvez envisager de rechercher isEmojiPresentation au lieu de isEmoji, car Apple indique ce qui suit pour isEmoji:

Cette propriété est vraie pour les scalaires rendus par défaut sous la forme emoji, ainsi que pour ceux qui ont un rendu emoji autre que celui par défaut, suivis de U + FE0F VARIATION SELECTOR-16. Cela inclut certains scalaires qui ne sont généralement pas considérés comme des emoji.


De cette façon, les Emoji sont divisés en tous les modificateurs, mais leur gestion est beaucoup plus simple. Et comme Swift compte maintenant Emoji avec modificateurs (par exemple: ???? ‍ ???? ‍ ???? ‍ ????, ???????? ‍? ???, ????) en tant que vous pouvez faire toutes sortes de choses.

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 indique un moyen intéressant d'obtenir tous les 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)
    }
}
5
alexkaessner

Swift 3 Note:

Il semble que le cnui_containsEmojiCharacters La méthode a été supprimée ou déplacée vers une autre bibliothèque dynamique. _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 NSString, qui expose des fonctionnalités permettant de 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 pure Swift, vous devez la convertir en AnyObject avant de pouvoir 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ête NSString .

4
JAL

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;
}
2
Ankit Goyal

La réponse absolument similaire à celle qui a été écrite avant moi, mais avec un ensemble de scalaires emoji mis à jour.

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

}
2
Alex Shoshiashvili

Preuve future: vérifier manuellement les pixels du personnage; les autres solutions vont casser (et ont casser) à mesure que de nouveaux emojis sont ajoutés.

Remarque: Ceci est Objective-C (peut être converti en Swift)

Au fil des ans, ces solutions de détection des emojis continuent à être utilisées Apple ajoute de nouveaux emojis avec de nouvelles méthodes (comme les emojis à la peau tonique construits en pré-maudissant un personnage avec un caractère supplémentaire), etc.

J'ai finalement craqué et juste écrit la méthode suivante qui fonctionne pour tous les emojis actuels et devrait fonctionner pour tous les futurs emojis.

La solution crée un UILabel avec le personnage et un arrière-plan noir. CG prend ensuite un instantané de l'étiquette et balaie tous les pixels de l'instantané à la recherche de pixels non noirs. La raison pour laquelle j’ajoute le fond noir est pour éviter les problèmes de fausse coloration dus à Subpixel Rendering

La solution tourne très vite sur mon appareil, je peux vérifier des centaines de caractères à la seconde, mais il convient de noter qu’il s’agit d’une solution CoreGraphics et qu’elle ne devrait pas être utilisée de la même manière que si vous utilisiez une méthode de texte ordinaire. Le traitement des graphiques est très chargé en données. La vérification de milliers de caractères à la fois peut entraîner un retard 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;

}
2
Albert Renshaw

Vous pouvez utiliser ce code exemple ou this pod .

Pour l'utiliser dans Swift, importez la catégorie dans le fichier YourProject_Bridging_Header

#import "NSString+EMOEmoji.h"

Ensuite, vous pouvez vérifier la plage de chaque émoticône de votre chaîne:

let example: NSString = "string????‍????‍????‍????with????emojis✊????" //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.

2
Gabriel.Massana

Vous pouvez utiliser NSString-RemoveEmoji comme ceci:

if string.isIncludingEmoji {

}
1
Shardul

Avec Swift 5, vous pouvez maintenant inspecter les propriétés unicode de chaque caractère de votre chaîne. Cela nous donne la variable pratique isEmoji de chaque lettre. Le problème est isEmoji retournera vrai pour tout caractère pouvant être converti en emoji sur 2 octets, tel que 0-9.

Nous pouvons examiner la variable isEmoji et également vérifier la présence d’un modificateur d’emoji pour déterminer si les caractères ambigus s’afficheront sous la forme d’un emoji.

Cette solution devrait être beaucoup plus durable que les solutions de regex proposées ici.

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

Nous donnant

"hey".containsEmoji() //false

"Hello World ????".containsEmoji() //true
"Hello World ????".containsOnlyEmojis() //false

"????".containsEmoji() //true
"????".containsOnlyEmojis() //true
1
Miniroo

Pour Swift 5.0:

Comptes pour:

  • Emoji non-Emoji (par exemple ASCII))
  • Jointeur de largeur zéro (compte ???? ???? ‍ ???? ‍ ???? en tant qu'un emoji, pas 4)
  • Modificateurs (par exemple, le teint de peau ????)
  • Sélecteurs de variation

Tout vrai:

"????".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
    }
}
0
beebcon