Aflați dacă un caracter din șirul de caractere este emoji? (Programare, Ios, String, Swift, Caracter, Emoji)

Andrew a intrebat.

Trebuie să aflu dacă un caracter dintr-un șir de caractere este un emoji.

De exemplu, am acest caracter:

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

Trebuie să aflu dacă acest caracter este un emoji.

Comentarii

  • Sunt curios: de ce aveți nevoie de această informație? –  > Por Martin R.
  • @EricD.: Există multe caractere Unicode care au mai mult de un punct de cod UTF-8 (de exemplu, „€” = E2 82 AC) sau mai mult de un punct de cod UTF-16 (de exemplu, ” ” =D834 DD1E). –  > Por Martin R.
  • Sper că vă veți face o idee din această versiune obj-c a codului stackoverflow.com/questions/19886642/… –  > Por Ashish Kakkad.
  • Șirurile de caractere au indexarea lor, care este o modalitate preferată de a le utiliza. Pentru a obține un anumit caracter (sau mai degrabă un grup de grafeme) ați putea: let character = string[string.index(after: string.startIndex)] sau let secondCharacter = string[string.index(string.startIndex, offsetBy: 1)] –  > Por Paul B.
13 răspunsuri
Kevin R

Ceea ce am dat peste este diferența dintre caractere, scalari unicode și glife.

De exemplu, glifa este formată din 7 scalari unicode:

Un alt exemplu, glifa este formată din 2 scalari unicode:

  • Emoji obișnuit:
  • Un modificator al nuanței pielii: .

Ultimul, glifa 1️⃣ conține trei caractere unicode:

Astfel, la redarea caracterelor, glifele rezultate contează cu adevărat.

Swift 5.0 și versiunile ulterioare facilitează foarte mult acest proces și ne scapă de unele presupuneri pe care trebuia să le facem. Unicode.Scalar‘s new Property ne ajută să determinăm cu ce avem de-a face. Cu toate acestea, aceste proprietăți au sens doar atunci când se verifică celelalte scalare din cadrul glifei. Din acest motiv, vom adăuga câteva metode convenabile clasei Character pentru a ne ajuta.

Pentru mai multe detalii, am scris un articol care explică cum funcționează acest lucru.

Pentru Swift 5.0, se obține următorul rezultat:

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

Ceea ce vă va da următoarele rezultate:

"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

Pentru versiunile Swift mai vechi, consultați acest gist care conține vechiul meu cod.

Comentarii

  • Acesta este de departe cel mai bun și mai corect răspuns aici. Vă mulțumesc! O mică observație, exemplele dvs. nu se potrivesc cu codul (ați redenumit containsOnlyEmoki în containsEmoji în fragment – presupun pentru că este mai corect, în testele mele a returnat true pentru șirurile cu caractere mixte). –  > Por Tim Bull.
  • Greșeala mea, am schimbat niște coduri, cred că am dat-o în bară. Am actualizat exemplul –  > Por Kevin R.
  • @Andrew: Sigur, am adăugat o altă metodă la exemplu pentru a demonstra acest lucru :). –  > Por Kevin R.
  • @Andrew aici este locul în care lucrurile devin cu adevărat dezordonate. Am adăugat un exemplu cum se face acest lucru. Problema este că mi-am asumat să știu cum CoreText va reda glifele prin simpla verificare a caracterelor. Dacă cineva are sugestii pentru o metodă mai curată, vă rog să mă anunțați. –  > Por Kevin R.
  • @Andrew Mulțumesc pentru că ai subliniat acest lucru, am schimbat modul în care containsOnlyEmoji verifică. De asemenea, am actualizat exemplul la Swift 3.0. –  > Por Kevin R.
Arnold

Cel mai simplu, mai curat și mai swiftiest este de a verifica pur și simplu punctele de cod Unicode pentru fiecare caracter din șirul de caractere în raport cu intervalele cunoscute de emoji și dingbats, astfel:

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
    }

}

Comentarii

  • Un astfel de exemplu de cod este mult mai bun decât să sugerezi includerea unei dependențe de o bibliotecă terță parte. Răspunsul lui Shardul este un sfat nechibzuit de urmat – scrieți-vă întotdeauna propriul cod. –  > Por thefaj.
  • Acest lucru este grozav, vă mulțumim că ați comentat la ce se referă cazurile –  > Por Shawn Throop.
  • Ca atât de mult codul tău, l-am implementat într-un răspuns aici. Un lucru pe care l-am observat este că lipsesc unele emoji, poate pentru că nu fac parte din categoriile pe care le-ați enumerat, de exemplu aceasta: Emoji cu față de robot –  > Por Cue.
  • @Tel Cred că ar fi vorba de gama 0x1F900...0x1F9FF (conform Wikipedia). Nu sunt sigur că toată gama ar trebui să fie considerată emoji. –  > Por Frizlab.
alexkaessner

Swift 5.0

… a introdus un nou mod de a verifica exact acest lucru!

Trebuie să vă rupeți String în Scalars. Fiecare Scalar are un Property care suportă valoarea isEmoji valoare!

De fapt, puteți chiar verifica dacă Scalar este un modificator Emoji sau mai mult. Consultați documentația Apple: https://developer.apple.com/documentation/swift/unicode/scalar/properties

Este posibil să doriți să luați în considerare verificarea pentru isEmojiPresentation în loc de isEmoji, deoarece Apple afirmă următoarele pentru isEmoji:

Această proprietate este adevărată pentru scalari care sunt redate ca emoji în mod implicit și, de asemenea, pentru scalari care au o redare emoji care nu este implicită atunci când sunt urmați de U+FE0F VARIATION SELECTOR-16. Aceasta include unele scalare care nu sunt considerate în mod obișnuit ca fiind emoji.


Acest mod împarte, de fapt, emoji-urile în toți modificatorii, dar este mult mai simplu de gestionat. Și cum Swift numără acum Emoji-urile cu modificatori (de exemplu: , , ) ca fiind 1, puteți face tot felul de lucruri.

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 semnalează o modalitate interesantă de a obține toate Emoji-urile:

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

Comentarii

  • Un răspuns excelent, mulțumesc. Merită menționat faptul că sdk-ul dvs. minim trebuie să fie 10.2 pentru a utiliza această parte din Swift 5. De asemenea, pentru a verifica dacă un șir de caractere este alcătuit doar din emoji, a trebuit să verific dacă are una dintre aceste proprietăți: scalar.properties.isEmoji scalar.properties.isEmojiPresentation scalar.properties.isEmojiModifier scalar.properties.isEmojiModifierBase scalar.properties.isJoinControl scalar.properties.isVariationSelector –  > Por A Springham.
  • Atenție, numerele întregi 0-9 sunt considerate emoji. Așadar, "6".unicodeScalars.first!.properties.isEmoji va fi evaluat ca true –  > Por Miniroo.
  • Există și alte caractere, cum ar fi # și * care, de asemenea, vor returna adevărat pentru isEmoji verificare. isEmojiPresentation pare să funcționeze mai bine, cel puțin returnează false pentru 0...9, #, * și orice alt simbol pe care am putut să-l încerc pe o tastatură englezo-americană. Are cineva mai multă experiență cu el și știe dacă poate fi de încredere pentru validarea intrărilor? –  > Por Jan.
Sebastian Lopez
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
    }
}

Aceasta este soluția mea, cu intervale actualizate.

Miniroo

Cu Swift 5 puteți inspecta acum proprietățile unicode ale fiecărui caracter din șirul dvs. de caractere. Acest lucru ne oferă o funcție convenabilă isEmoji variabilă pe fiecare literă. Problema este următoarea isEmoji va returna true pentru orice caracter care poate fi convertit într-un emoji de 2 octeți, cum ar fi 0-9.

Ne putem uita la variabila isEmoji și, de asemenea, să verificăm prezența unui modificator emoji pentru a determina dacă caracterele ambigue vor fi afișate ca emoji.

Această soluție ar trebui să fie mult mai rezistentă în viitor decât soluțiile regex oferite aici.

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

Oferindu-ne

"hey".containsEmoji() //false

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

"3".containsEmoji() //false
"3️⃣".containsEmoji() //true

Comentarii

  • Și mai mult decât atât Character("3️⃣").isEmoji // true în timp ce Character("3").isEmoji // false –  > Por Paul B.
JAL

Swift 3 Notă:

Se pare că cnui_containsEmojiCharacters fie a fost eliminată, fie a fost mutată într-o altă bibliotecă dinamică. _containsEmoji ar trebui să funcționeze în continuare, totuși.

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:

Am descoperit recent un API privat pe NSString care expune funcționalitatea pentru a detecta dacă un șir de caractere conține un caracter Emoji:

let str: NSString = "hello "

Cu un protocol 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

Cu valueForKey:

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

Cu un șir de caractere Swift pur, trebuie să transformați șirul în AnyObject înainte de a utiliza valueForKey:

let str = "hello "

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

Metodele care se găsesc în fișier antet NSString.

Comentarii

  • Aceasta este ceea ce caut, Mulțumesc JAL – user5180348
  • Acest lucru va fi respins de Apple? –  > Por Andrey Chernukha.
  • @AndreyChernukha Există întotdeauna un risc, dar nu am experimentat încă nici o respingere. –  > Por JAL.
  • Nu folosiți niciodată API-uri private. În cel mai bun caz, răul va veni abia mâine. Sau luna viitoare. –  > Por xaphod.
Gabriel.Massana

Puteți folosi acest cod exemplu sau acesta pod.

Pentru a-l utiliza în Swift, importați categoria în YourProject_Bridging_Header

#import "NSString+EMOEmoji.h"

Apoi puteți verifica intervalul pentru fiecare emoji din String:

let example: NSString = "string ‍ ‍ ‍ with emojis✊ " //string with emojis

let containsEmoji: Bool = example.emo_containsEmoji()

    print(containsEmoji)

// Output: ["true"]

Am creat un mic proiect de exemplu cu codul de mai sus.

Albert Renshaw

Future Proof: Verificați manual pixelii caracterului; celelalte soluții se vor defecta (și s-au defectat) pe măsură ce se adaugă noi emoji-uri.

Notă: Acesta este Objective-C (poate fi convertit în Swift)

De-a lungul anilor, aceste soluții de detectare a emoji-urilor continuă să se rupă pe măsură ce Apple adaugă noi emoji-uri cu metode noi (cum ar fi emoji-urile cu nuanțe de piele construite prin preîncredințarea unui caracter cu un caracter suplimentar) etc.

În cele din urmă am cedat și am scris următoarea metodă care funcționează pentru toate emoji-urile actuale și ar trebui să funcționeze pentru toate emoji-urile viitoare.

Soluția creează un UILabel cu caracterul și un fundal negru. CG ia apoi o imagine instantanee a etichetei și scanez toți pixelii din imagine pentru orice pixel care nu este negru solid. Motivul pentru care adaug fundalul negru este pentru a evita problemele de colorare falsă datorate Renderizarea subpixelilor

Soluția rulează FOARTE repede pe dispozitivul meu, pot verifica sute de caractere pe secundă, dar trebuie remarcat faptul că aceasta este o soluție CoreGraphics și nu ar trebui utilizată intensiv, așa cum s-ar putea face cu o metodă obișnuită de text. Prelucrarea grafică este o procesare de date grele, astfel încât verificarea a mii de caractere deodată ar putea duce la un decalaj notabil.

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

Comentarii

  • Îmi place cum gândești! 😉 – E o idee inedită! –  > Por Ramon.
  • De ce ne faceți asta? #apple #unicodestandard .  > Por d4Rk.
  • Nu m-am mai uitat la acest lucru de ceva timp, dar mă întreb dacă trebuie să convertesc în UIColor și apoi în hsb; se pare că pot verifica doar dacă r,g,b toate == 0? Dacă cineva încearcă să mă anunțe –  > Por Albert Renshaw.
  • îmi place această soluție, dar nu se va rupe cu un caracter ca ℹ ? –  > Por Juan Carlos Ospina Gonzalez.
  • @JuanCarlosOspinaGonzalez Nu, în emoji, care se redă ca o cutie albastră cu un i alb. Totuși, aduce un punct bun că UILabel ar trebui să forțeze fontul să fie AppleColorEmoji, adăugând că în acum ca o siguranță, deși cred că Apple va fi implicit pentru cei oricum –  > Por Albert Renshaw.
Ankit Goyal

Pentru Swift 3.0.2, următorul răspuns este cel mai simplu:

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

Alex Shoshiashvili

Răspunsul absolut similar cu cei care au scris înaintea mea, dar cu setul actualizat de scalari 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
        }
    }

}

Shardul

Puteți utiliza NSString-RemoveEmoji în felul următor:

if string.isIncludingEmoji {

}

Dmytro Babych

Există o soluție frumoasă pentru sarcina menționată. Dar verificarea Unicode.Scalar.Properties of Unicode scalars este bună pentru un singur caracter. Și nu este suficient de flexibilă pentru șiruri de caractere.

În schimb, putem utiliza expresii regulate – o abordare mai universală. Mai jos există o descriere detaliată a modului în care funcționează. Și iată soluția.

Soluția

În Swift puteți verifica dacă un șir de caractere este un singur caracter Emoji, utilizând o extensie cu o astfel de proprietate calculată:

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
    }

}

Cum funcționează (în detaliu)

Un singur Emoji (un glif) poate fi reprodus printr-un număr de simboluri diferite, secvențe și combinații ale acestora.Specificația Unicode definește mai multe reprezentări posibile ale caracterelor Emoji.

Emoji cu un singur caracter

Un caracter Emoji reprodus de un singur Unicode Scalar.

Unicode definește caracterul Emoji ca fiind:

emoji_character := p{Emoji}

Dar asta nu înseamnă neapărat că un astfel de caracter va fi desenat ca Emoji. Un simbol numeric obișnuit „1” are proprietatea Emoji ca fiind adevărată, deși ar putea fi desenat ca text. Și există o listă de astfel de simboluri: #, ©, 4, etc.

Ar trebui să ne gândim că putem folosi o proprietate suplimentară pentru a verifica: „Emoji_Presentation”. Dar nu funcționează astfel. Există un Emoji ca sau , care are proprietatea Emoji_Presentation=false.

Pentru a ne asigura că personajul este desenat ca Emoji în mod implicit, trebuie să verificăm categoria acestuia: ar trebui să fie „Other_symbol”.

Deci, de fapt, expresia regulată pentru Emoji cu un singur caracter ar trebui să fie definită astfel:

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

Secvența de prezentare a emoji-urilor

Un caracter care, în mod normal, poate fi desenat fie ca text, fie ca Emoji. Aspectul său depinde de un simbol special care urmează, un selector de prezentare, care indică tipul de prezentare. x{FE0E} definește reprezentarea textului. x{FE0F} definește reprezentarea emoji.

Lista acestor simboluri poate fi găsită [aici](
https://unicode.org/Public/emoji/12.1/emoji-variation-sequences.txt).

Unicode definește secvența de prezentare în felul următor:

emoji_presentation_sequence := emoji_character emoji_presentation_selector

Secvență de expresii regulate pentru aceasta:

emoji_presentation_sequence := p{Emoji} x{FE0F}

Secvența de caractere Emoji Keycap

Secvența arată foarte asemănător cu secvența de prezentare, dar are un scalar suplimentar la sfârșit: x{20E3}. Domeniul de aplicare al scalarilor de bază posibili utilizați pentru această secvență este destul de restrâns: 0-9#* – și asta e tot. Exemple: 1️⃣, 8️⃣, *️⃣.

Unicode definește secvența de taste astfel:

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

Expresie regulată pentru aceasta:

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

Secvența modificatoare Emoji

Unele Emoji pot avea aspectul modificat, cum ar fi un ton de piele. De exemplu, Emoji poate fi diferit: . Pentru a defini un Emoji, care se numește în acest caz „Emoji_Modifier_Base”, se poate utiliza un „Emoji_Modifier” ulterior.

În general, o astfel de secvență arată astfel:

emoji_modifier_sequence := emoji_modifier_base emoji_modifier

Pentru a o detecta, putem căuta o secvență de expresii regulate:

emoji_modifier_sequence := p{Emoji} p{EMod}

Secvența de steaguri Emoji

Steagurile sunt Emoji-uri cu structura lor specială. Fiecare steag este reprezentat prin două simboluri „Regional_Indicator”.

Unicode le definește astfel:

emoji_flag_sequence := regional_indicator regional_indicator

De exemplu, steagul Ucrainei de fapt este reprezentat cu două scalare: u{0001F1FA u{0001F1E6}

Expresie regulată pentru aceasta:

emoji_flag_sequence := p{RI}{2}

Secvență de etichete emoji (ETS)

O secvență care utilizează o așa-numită tag_base, urmată de o specificație de etichetă personalizată compusă dintr-o serie de simboluri x{E0020}-x{E007E} și încheiată de marca tag_end x{E007F}.

Unicode îl definește astfel:

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}

Ciudat este că Unicode permite ca eticheta să se bazeze pe emoji_modifier_sequence sau emoji_presentation_sequence în ED-14a. Dar, în același timp, în expresiile regulate furnizate la aceeași documentație acestea par să verifice secvența pe baza unui singur caracter Emoji.

În lista Unicode 12.1 Emoji există doar trei astfel de Emoji definite. Toate acestea sunt steaguri ale țărilor din Regatul Unit: Anglia , Scoția și Țara Galilor . Și toate se bazează pe un singur caracter Emoji. Așadar, ar fi bine să verificăm doar pentru o astfel de secvență.

Expresie regulată:

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

Secvența Emoji Zero-Width Joiner (secvența ZWJ)

Un joiner cu lățime zero este un scalar x{200D}. Cu ajutorul acestuia, mai multe caractere, care sunt deja Emoji, pot fi combinate în altele noi.

De exemplu, un Emoji „familie cu tată, fiu și fiică” este reprodus printr-o combinație de Emoji-uri tată , fiică și fiu lipite împreună cu simboluri ZWJ.

Este permisă lipirea elementelor, care sunt caractere Emoji unice, secvențe de prezentare și secvențe de modificare.

Expresia regulată pentru o astfel de secvență în general arată astfel:

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

Expresie regulată pentru toate aceste secvențe

Toate reprezentările Emoji menționate mai sus pot fi descrise printr-o singură expresie regulată:

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

Juan Carlos Ospina Gonzalez

am avut aceeași problemă și am ajuns să fac o String și Character extensii.

Codul este prea lung pentru a fi postat, deoarece listează de fapt toate emoji-urile (din lista oficială Unicode v5.0) într-o CharacterSet îl puteți găsi aici:

https://github.com/piterwilson/StringEmoji

Constante

let emojiCharacterSet: CharacterSet

Set de caractere care conține toate emoji-urile cunoscute (așa cum sunt descrise în lista oficială Unicode List 5.0 http://unicode.org/emoji/charts-5.0/emoji-list.html)

String

var isEmoji: Bool { get }

Dacă este sau nu este String instanță reprezintă un singur caracter Emoji cunoscut

print("".isEmoji) // false
print(" ".isEmoji) // true
print(" ".isEmoji) // false (String is not a single Emoji)

var containsEmoji: Bool { get }

Dacă instanța este sau nu String instanța conține un caracter Emoji cunoscut

print("".containsEmoji) // false
print(" ".containsEmoji) // true
print(" ".containsEmoji) // true

var unicodeName: String { get }

Aplică un kCFStringTransformToUnicodeNameCFStringTransform pe o copie a String-ului

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

var niceUnicodeName: String { get }

Întoarce rezultatul unui kCFStringTransformToUnicodeNameCFStringTransform cu N{ prefixe și } sufixe eliminate

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

Caracter

v

Indiferent dacă este sau nu Character instanță reprezintă un caracter Emoji cunoscut

print("".isEmoji) // false
print(" ".isEmoji) // true