Dowiedz się, czy znak w ciągu znaków to emoji?

90

Muszę się dowiedzieć, czy znak w ciągu znaków to emoji.

Na przykład mam tę postać:

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

Muszę się dowiedzieć, czy ta postać to emoji.

Andrzej
źródło
Jestem ciekaw: po co ci te informacje?
Martin R
@EricD .: Istnieje wiele znaków Unicode, które zajmują więcej niż jeden punkt kodowy UTF-8 (np. „€” = E2 82 AC) lub więcej niż jeden punkt kodowy UTF-16 (np. „𝄞” = D834 DD1E).
Martin R
Mam nadzieję, że wpadniesz na pomysł z tej wersji kodu obj-c stackoverflow.com/questions/19886642/ ...
Ashish Kakkad
Łańcuchy mają swoje indeksowanie, co jest preferowanym sposobem ich używania. Aby uzyskać konkretny znak (lub raczej klaster grafemów), możesz: let character = string[string.index(after: string.startIndex)]lub let secondCharacter = string[string.index(string.startIndex, offsetBy: 1)]
Paul B

Odpowiedzi:

228

To, na co się natknąłem, to różnica między postaciami, skalarami Unicode i glifami.

Na przykład glif 👨‍👨‍👧‍👧 składa się z 7 skalarów Unicode:

Inny przykład, glif 👌🏿 składa się z 2 skalarów Unicode:

  • Zwykły emoji: 👌
  • Modyfikator odcienia skóry: 🏿

Ostatni, glif 1️⃣ zawiera trzy znaki Unicode:

Tak więc podczas renderowania znaków otrzymane glify naprawdę mają znaczenie.

Swift 5.0 i nowsze wersje znacznie ułatwiają ten proces i pozwalają pozbyć się domysłów, które musieliśmy wykonać. Unicode.ScalarNowy Propertytyp pomaga określić, z czym mamy do czynienia. Jednak te właściwości mają sens tylko podczas sprawdzania innych skalarów w glifie. Dlatego dodamy kilka wygodnych metod do klasy Character, aby nam pomóc.

Aby uzyskać więcej szczegółów, napisałem artykuł wyjaśniający, jak to działa .

W przypadku Swift 5.0 pozostawia następujący wynik:

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

Który da następujące wyniki:

"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

W przypadku starszych wersji Swift zapoznaj się z tym streszczeniem zawierającym mój stary kod.

Kevin R.
źródło
6
To zdecydowanie najlepsza i najbardziej poprawna odpowiedź. Dziękuję Ci! Jedna mała uwaga, twoje przykłady nie pasują do kodu (zmieniono nazwę z zawieraOnlyEmoki na zawieraEmoji we fragmencie - zakładam, że jest to bardziej poprawne, w moich testach zwróciło to prawdę dla ciągów z mieszanymi znakami).
Tim Bull,
3
Mój błąd, zmieniłem jakiś kod, chyba zawiodłem. Zaktualizowałem przykład
Kevin R
2
@Andrew: Jasne, dodałem do przykładu inną metodę, aby to zademonstrować :).
Kevin R
2
@ Andrew, tutaj robi się naprawdę bałagan. Dodałem przykład, jak to zrobić. Problem polega na tym, że zakładam, że wiem, jak CoreText wyrenderuje glify, po prostu sprawdzając znaki. Jeśli ktoś ma sugestie dotyczące czystszej metody, daj mi znać.
Kevin R
3
@Andrew Dzięki za zwrócenie uwagi, zmieniłem sposób containsOnlyEmojisprawdzania. Zaktualizowałem również przykład do Swift 3.0.
Kevin R
48

Najprostszym, najczystszym i najszybszym sposobem osiągnięcia tego jest po prostu sprawdzenie punktów kodowych Unicode dla każdego znaku w ciągu względem znanych zakresów emoji i dingbatów, na przykład:

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
    }

}
Arnold
źródło
9
Taki przykład kodu jest o wiele lepszy niż sugerowanie uwzględnienia zależności biblioteki innej firmy. Odpowiedź Shardula to nierozsądna rada do naśladowania - zawsze pisz własny kod.
thefaj
To wspaniale, dziękuję za komentarz, którego dotyczą sprawy
Shawn Throop
1
Podobnie jak twój kod, zaimplementowałem go w odpowiedzi tutaj . Zauważyłem, że brakuje niektórych emoji, być może dlatego, że nie należą one do wymienionych kategorii, na przykład ta: Emotikony Robot Face 🤖
Cue
1
@Tel Myślę, że byłby to zakres 0x1F900...0x1F9FF(na Wikipedii). Nie jestem pewien, czy cały zakres należy uznać za emoji.
Frizlab
8
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
    }
}

To jest moja poprawka ze zaktualizowanymi zakresami.

Sebastian Lopez
źródło
8

Swift 5.0

… Wprowadził nowy sposób sprawdzenia dokładnie tego!

Musisz złamać Stringw jego Scalars. Każdy Scalarma Propertywartość, która wspiera tę isEmojiwartość!

Właściwie możesz nawet sprawdzić, czy Skalar jest modyfikatorem Emoji, czy więcej. Sprawdź dokumentację Apple: https://developer.apple.com/documentation/swift/unicode/scalar/properties

Możesz rozważyć sprawdzenie isEmojiPresentationzamiast isEmoji, ponieważ Apple stwierdza, co następuje isEmoji:

Ta właściwość jest prawdziwa dla skalarów, które są domyślnie renderowane jako emoji, a także dla skalarów, które mają inne niż domyślne renderowanie emoji, po których następuje U + FE0F VARIATION SELECTOR-16. Obejmuje to niektóre skalary, które zwykle nie są uważane za emoji.


W ten sposób faktycznie dzieli Emoji na wszystkie modyfikatory, ale jest o wiele prostszy w obsłudze. A ponieważ Swift liczy teraz emotikony z modyfikatorami (np .: 👨‍👩‍👧‍👦, 👨🏻‍💻, 🏴) jako 1, możesz robić wszelkiego rodzaju rzeczy.

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 wskazuje ciekawy sposób na zdobycie wszystkich emotikonów:

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)
    }
}
alexkaessner
źródło
1
Świetna odpowiedź, dzięki. Warto wspomnieć, że minimalny sdk musi wynosić 10,2, aby móc korzystać z tej części Swift 5. Również w celu sprawdzenia, czy ciąg znaków składa się tylko z emotikonów, musiałem sprawdzić, czy ma jedną z tych właściwości:scalar.properties.isEmoji scalar.properties.isEmojiPresentation scalar.properties.isEmojiModifier scalar.properties.isEmojiModifierBase scalar.properties.isJoinControl scalar.properties.isVariationSelector
A Springham
6
Uwaga, liczby całkowite od 0 do 9 są uważane za emotikony. Więc "6".unicodeScalars.first!.properties.isEmojioceni jakotrue
Miniroo
6

Dzięki Swift 5 możesz teraz sprawdzić właściwości Unicode każdego znaku w ciągu. To daje nam wygodną isEmojizmienną na każdej literze. Problem polega na tym, isEmojiże powróci prawda dla każdego znaku, który można przekształcić w 2-bajtowe emoji, na przykład 0-9.

Możemy spojrzeć na zmienną, isEmojia także sprawdzić obecność modyfikatora emoji, aby określić, czy niejednoznaczne znaki będą wyświetlane jako emoji.

To rozwiązanie powinno być dużo bardziej przyszłościowe niż oferowane tutaj rozwiązania regex.

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

Dając nam

"hey".containsEmoji() //false

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

"3".containsEmoji() //false
"3️⃣".containsEmoji() //true
Miniroo
źródło
1
A co więcej, Character("3️⃣").isEmoji // truegdyCharacter("3").isEmoji // false
Paul B
4

Swift 3 Uwaga:

Wygląda na to, że cnui_containsEmojiCharactersmetoda została usunięta lub przeniesiona do innej biblioteki dynamicznej. _containsEmojipowinien jednak nadal działać.

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:

Niedawno odkryłem prywatny interfejs API, NSStringktóry udostępnia funkcje wykrywania, czy ciąg zawiera znak Emoji:

let str: NSString = "hello😊"

Z protokołem objc i unsafeBitCast:

@objc protocol NSStringPrivate {
    func cnui_containsEmojiCharacters() -> ObjCBool
    func _containsEmoji() -> ObjCBool
}

let strPrivate = unsafeBitCast(str, NSStringPrivate.self)
strPrivate.cnui_containsEmojiCharacters() // true
strPrivate._containsEmoji() // true

Z valueForKey:

str.valueForKey("cnui_containsEmojiCharacters") // 1
str.valueForKey("_containsEmoji") // 1

W przypadku czystej struny Swift musisz zarzucić strunę tak, jak AnyObjectprzed użyciem valueForKey:

let str = "hello😊"

(str as AnyObject).valueForKey("cnui_containsEmojiCharacters") // 1
(str as AnyObject).valueForKey("_containsEmoji") // 1

Metody znalezione w pliku nagłówkowym NSString .

JAL
źródło
Właśnie tego szukam, dzięki JAL
Czy zostanie to odrzucone przez Apple?
Andrey Chernukha
@AndreyChernukha Zawsze istnieje ryzyko, ale nie doświadczyłem jeszcze żadnego odrzucenia.
JAL
Nigdy, przenigdy nie używaj prywatnych interfejsów API. W najlepszym razie ból nadejdzie dopiero jutro. Albo w przyszłym miesiącu.
xaphod
3

Możesz użyć tego przykładu kodu lub tego pod .

Aby użyć go w Swift, zaimportuj kategorię do YourProject_Bridging_Header

#import "NSString+EMOEmoji.h"

Następnie możesz sprawdzić zakres każdego emoji w swoim ciągu:

let example: NSString = "string👨‍👨‍👧‍👧with😍emojis✊🏿" //string with emojis

let containsEmoji: Bool = example.emo_containsEmoji()

    print(containsEmoji)

// Output: ["true"]

Stworzyłem mały przykładowy projekt z powyższym kodem.

Gabriel.Massana
źródło
3

Future Proof: Ręcznie sprawdź piksele postaci; inne rozwiązania zepsują się (i zepsuły) po dodaniu nowych emoji.

Uwaga: to jest Objective-C (można przekonwertować na Swift)

Z biegiem lat te rozwiązania do wykrywania emoji wciąż się psują, gdy Apple dodaje nowe emoji w / nowe metody (takie jak emoji o odcieniu skóry zbudowane przez wstępne przeklinanie postaci z dodatkową postacią) itp.

W końcu się zepsułem i właśnie napisałem następującą metodę, która działa dla wszystkich obecnych emoji i powinna działać dla wszystkich przyszłych emoji.

Rozwiązanie tworzy UILabel z postacią i czarnym tłem. Następnie CG robi migawkę etykiety i skanuję wszystkie piksele w migawce w poszukiwaniu jakichkolwiek innych niż jednolicie czarne piksele. Powodem dodania czarnego tła jest uniknięcie problemów z fałszywym kolorowaniem z powodu renderowania subpikseli

Rozwiązanie działa BARDZO szybko na moim urządzeniu, mogę sprawdzić setki znaków na sekundę, ale należy zauważyć, że jest to rozwiązanie CoreGraphics i nie powinno być używane tak intensywnie, jak w przypadku zwykłej metody tekstowej. Przetwarzanie grafiki wymaga dużej ilości danych, więc sprawdzenie tysięcy znaków jednocześnie może spowodować zauważalne opóźnienie.

-(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;
    
}
Albert Renshaw
źródło
4
Lubię Twoje myślenie! ;) - Po wyjęciu z pudełka!
Ramon
Dlaczego nam to robisz? #apple #unicodestandard 😱🤔🤪🙈😈🤕💩
d4Rk
Nie patrzyłem na to od jakiegoś czasu, ale zastanawiam się, czy muszę przekonwertować na UIColor, a następnie na hsb; wygląda na to, że mogę po prostu sprawdzić, czy r, g, b all == 0? Jeśli ktoś spróbuje, daj mi znać
Albert Renshaw
Podoba mi się to rozwiązanie, ale czy nie zerwie się z takim znakiem jak ℹ?
Juan Carlos Ospina Gonzalez
1
@JuanCarlosOspinaGonzalez Nope, w emoji, które renderuje się jako niebieskie pudełko z białym i. To jednak dobra uwaga, że ​​UILabel powinien wymusić na czcionce AppleColorEmoji, dodając to teraz jako zabezpieczenie przed awarią, chociaż myślę, że Apple i tak zrobi to domyślnie
Albert Renshaw
2

W przypadku Swift 3.0.2 następująca odpowiedź jest najprostsza:

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;
}
Ankit Goyal
źródło
2

Absolutnie podobna odpowiedź do tych, które napisałem przede mną, ale z zaktualizowanym zestawem skalarów 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
        }
    }

}
Alex Shoshiashvili
źródło
0

Na wspomniane zadanie jest fajne rozwiązanie . Ale sprawdzenie właściwości Unicode.Scalar.Properties skalarów Unicode jest dobre dla pojedynczego znaku. I nie jest wystarczająco elastyczny dla Strings.

Zamiast tego możemy użyć wyrażeń regularnych - bardziej uniwersalne podejście. Poniżej znajduje się szczegółowy opis tego, jak to działa. I oto rozwiązanie.

Rozwiązanie

W Swift możesz sprawdzić, czy String jest pojedynczym znakiem Emoji, używając rozszerzenia z taką obliczoną właściwością:

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
    }

}

Jak to działa (w szczegółach)

Pojedynczy emoji (glif) można odtworzyć za pomocą wielu różnych symboli, sekwencji i ich kombinacji. Specyfikacja Unicode definiuje kilka możliwych reprezentacji znaków Emoji.

Emotikony jednoznakowe

Znak Emoji odtwarzany przez pojedynczy skalar Unicode.

Unicode definiuje znak Emoji jako:

emoji_character := \p{Emoji}

Ale niekoniecznie oznacza to, że taka postać zostanie narysowana jako Emoji. Zwykły symbol liczbowy „1” ma właściwość Emoji, która ma wartość true, chociaż nadal może być narysowany jako tekst. I jest lista takich symboli: #, ©, 4 itd.

Należy pomyśleć, że możemy wykorzystać dodatkową właściwość do zaznaczenia: „Emoji_Presentation”. Ale to tak nie działa. Istnieje emoji, takie jak 🏟 lub 🛍, które mają właściwość Emoji_Presentation = false.

Aby mieć pewność, że postać jest domyślnie rysowana jako Emoji, powinniśmy sprawdzić jej kategorię: powinna to być „Inny_symbol”.

Tak więc w rzeczywistości wyrażenie regularne dla emoji jednoznakowych powinno być zdefiniowane jako:

emoji_character := \p{Emoji}&&\p{Other_symbol}

Sekwencja prezentacji emotikonów

Znak, który normalnie można narysować jako tekst lub jako emoji. Jego wygląd uzależniony jest od specjalnego symbolu następującego po nim, selektora prezentacji, który wskazuje typ prezentacji. \ x {FE0E} definiuje reprezentację tekstu. \ x {FE0F} definiuje reprezentację emoji.

Listę takich symboli można znaleźć [tutaj] (
 https://unicode.org/Public/emoji/12.1/emoji-variation-sequences.txt ).

Unicode definiuje sekwencję prezentacji w następujący sposób:

emoji_presentation_sequence := emoji_character emoji_presentation_selector

Sekwencja wyrażeń regularnych:

emoji_presentation_sequence := \p{Emoji} \x{FE0F}

Sekwencja klawiszy emoji

Sekwencja wygląda bardzo podobnie jak sekwencja prezentacji, ale ma na końcu dodatkowy skalar: \ x {20E3}. Zakres możliwych do tego bazowych skalarów jest dość wąski: 0-9 # * - i to wszystko. Przykłady: 1️⃣, 8️⃣, * ️⃣.

Unicode definiuje sekwencję klawiszy w następujący sposób:

emoji_keycap_sequence := [0-9#*] \x{FE0F 20E3}

Wyrażenie regularne:

emoji_keycap_sequence := \p{Emoji} \x{FE0F} \x{FE0F}

Sekwencja modyfikatorów emotikonów

Niektóre emotikony mogą mieć zmodyfikowany wygląd, podobny do odcienia skóry. Na przykład Emoji 🧑 może być inne: 🧑🧑🏻🧑🏼🧑🏽🧑🏾🧑🏿. Aby zdefiniować Emoji, które w tym przypadku nazywa się „Emoji_Modifier_Base”, można użyć kolejnego „Emoji_Modifier”.

Generalnie taka sekwencja wygląda następująco:

emoji_modifier_sequence := emoji_modifier_base emoji_modifier

Aby to wykryć, możemy wyszukać sekwencję wyrażeń regularnych:

emoji_modifier_sequence := \p{Emoji} \p{EMod}

Sekwencja flagi emoji

Flagi to emotikony o określonej strukturze. Każda flaga jest reprezentowana przez dwa symbole „Regional_Indicator”.

Unicode definiuje je tak:

emoji_flag_sequence := regional_indicator regional_indicator

Na przykład flaga Ukrainy 🇺🇦 w rzeczywistości jest reprezentowana przez dwa skalary: \ u {0001F1FA \ u {0001F1E6}

Wyrażenie regularne:

emoji_flag_sequence := \p{RI}{2}

Sekwencja znaczników emoji (ETS)

Sekwencja wykorzystująca tak zwaną podstawę_tagu, po której następuje niestandardowa specyfikacja znacznika złożona z zakresu symboli \ x {E0020} - \ x {E007E} i zakończona znacznikiem końca_tagu \ x {E007F}.

Unicode definiuje to następująco:

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}

Dziwne jest to, że Unicode pozwala na oparcie tagu na emoji_modifier_sequence lub emoji_presentation_sequence w ED-14a . Ale jednocześnie w wyrażeniach regularnych dostarczonych w tej samej dokumentacji wydają się sprawdzać sekwencję na podstawie tylko jednego znaku Emoji.

Na liście Emoji Unicode 12.1 są tylko trzy takie Emoji zdefiniowane . Wszystkie są flagami krajów Wielkiej Brytanii: Anglii 🏴󠁧󠁢󠁥󠁮󠁧󠁿, Szkocji 🏴󠁧󠁢󠁳󠁣󠁴󠁿 i Walii 🏴󠁧󠁢󠁷󠁬󠁳󠁿. Wszystkie są oparte na jednej postaci Emoji. Więc lepiej sprawdźmy tylko taką sekwencję.

Wyrażenie regularne:

\p{Emoji} [\x{E0020}-\x{E007E}]+ \x{E007F}

Sekwencja łączenia emoji o zerowej szerokości (sekwencja ZWJ)

Łącznik o zerowej szerokości to wartość skalarna \ x {200D}. Z jego pomocą kilka postaci, które są już same w sobie Emoji, można połączyć w nowe.

Na przykład „rodzina z ojcem, synem i córką” Emoji 👨‍👧‍👦 jest reprodukowana przez połączenie emotikonów ojciec 👨, córka 👧 i syn 👦 sklejone z symbolami ZWJ.

Dozwolone jest łączenie ze sobą elementów, którymi są pojedyncze znaki Emoji, sekwencje Prezentacji i Modyfikatorów.

Wyrażenie regularne dla takiej sekwencji ogólnie wygląda następująco:

emoji_zwj_sequence := emoji_zwj_element (\x{200d} emoji_zwj_element )+

Wyrażenie regularne dla nich wszystkich

Wszystkie wymienione powyżej reprezentacje Emoji można opisać za pomocą jednego wyrażenia regularnego:

\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}] 
    ) 
  )*
Dmytro Babych
źródło
-1

miałem ten sam problem i skończyło się na zrobieniu rozszerzenia Stringi Character.

Kod jest zbyt długi, aby go opublikować, ponieważ w rzeczywistości zawiera listę wszystkich emotikonów (z oficjalnej listy Unicode v5.0) w pliku, CharacterSetktóry można znaleźć tutaj:

https://github.com/piterwilson/StringEmoji

Stałe

niech emojiCharacterSet: CharacterSet

Zestaw znaków zawierający wszystkie znane emoji (zgodnie z opisem na oficjalnej liście Unicode 5.0 http://unicode.org/emoji/charts-5.0/emoji-list.html )

Strunowy

var isEmoji: Bool {get}

Określa, czy Stringinstancja reprezentuje znany pojedynczy znak Emoji

print("".isEmoji) // false
print("😁".isEmoji) // true
print("😁😜".isEmoji) // false (String is not a single Emoji)
var zawieraEmoji: Bool {get}

Określa, czy Stringinstancja zawiera znany znak Emoji

print("".containsEmoji) // false
print("😁".containsEmoji) // true
print("😁😜".containsEmoji) // true
var unicodeName: String {get}

Stosuje kCFStringTransformToUnicodeName- CFStringTransformna kopii String

print("á".unicodeName) // \N{LATIN SMALL LETTER A WITH ACUTE}
print("😜".unicodeName) // "\N{FACE WITH STUCK-OUT TONGUE AND WINKING EYE}"
var niceUnicodeName: String {get}

Zwraca wynik kCFStringTransformToUnicodeName- CFStringTransformz usuniętymi \N{przedrostkami i }sufiksami

print("á".unicodeName) // LATIN SMALL LETTER A WITH ACUTE
print("😜".unicodeName) // FACE WITH STUCK-OUT TONGUE AND WINKING EYE

Postać

var isEmoji: Bool {get}

Określa, czy Characterinstancja reprezentuje znany znak Emoji

print("".isEmoji) // false
print("😁".isEmoji) // true
Juan Carlos Ospina Gonzalez
źródło