@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).
Ł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:
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.
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:
extensionString{
var containsEmoji: Bool {
for scalar in unicodeScalars {
switch scalar.value {
case0x1F600...0x1F64F, // Emoticons0x1F300...0x1F5FF, // Misc Symbols and Pictographs0x1F680...0x1F6FF, // Transport and Map0x2600...0x26FF, // Misc symbols0x2700...0x27BF, // Dingbats0xFE00...0xFE0F, // Variation Selectors0x1F900...0x1F9FF, // Supplemental Symbols and Pictographs0x1F1E6...0x1F1FF: // Flagsreturntruedefault:
continue
}
}
returnfalse
}
}
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
extensionString{
funccontainsEmoji() -> Bool {
for scalar in unicodeScalars {
switch scalar.value {
case0x3030, 0x00AE, 0x00A9,// Special Characters0x1D000...0x1F77F, // Emoticons0x2100...0x27BF, // Misc symbols and Dingbats0xFE00...0xFE0F, // Variation Selectors0x1F900...0x1F9FF: // Supplemental Symbols and Pictographsreturntruedefault:
continue
}
}
returnfalse
}
}
To jest moja poprawka ze zaktualizowanymi zakresami.
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 in0x0000...0x1F0000 {
guardlet scalarValue = Unicode.Scalar(codePoint) else {
continue
}
// Implemented in Swift 5 (SE-0221)// https://github.com/apple/swift-evolution/blob/master/proposals/0221-character-properties.mdif scalarValue.properties.isEmoji {
emoji.insert(scalarValue)
}
}
Ś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.
extensionString{
funccontainsOnlyEmojis() -> Bool {
ifcount == 0 {
returnfalse
}
for character inself {
if !character.isEmoji {
returnfalse
}
}
returntrue
}
funccontainsEmoji() -> Bool {
for character inself {
if character.isEmoji {
returntrue
}
}
returnfalse
}
}
extensionCharacter{
// An emoji can either be a 2 byte unicode character or a normal UTF8 character with an emoji modifier// appended as is the case with 3️⃣. 0x238C is the first instance of UTF16 emoji that requires no modifier.// `isEmoji` will evaluate to true for any character that can be turned into an emoji by adding a modifier// such as the digit "3". To avoid this we confirm that any character below 0x238C has an emoji modifier attachedvar isEmoji: Bool {
guardlet scalar = unicodeScalars.first else { returnfalse }
return scalar.properties.isEmoji && (scalar.value > 0x238C || unicodeScalars.count > 1)
}
}
Dając nam
"hey".containsEmoji() //false"Hello World 😎".containsEmoji() //true"Hello World 😎".containsOnlyEmojis() //false"3".containsEmoji() //false"3️⃣".containsEmoji() //true
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😊"@objcprotocolNSStringPrivate{
func_containsEmoji() -> ObjCBool
}
let strPrivate = unsafeBitCast(str, to: NSStringPrivate.self)
strPrivate._containsEmoji() // true
str.value(forKey: "_containsEmoji") // 1let swiftStr = "hello😊"
(swiftStr asAnyObject).value(forKey: "_containsEmoji") // 1
Swift 2.x:
Niedawno odkryłem prywatny interfejs API, NSStringktóry udostępnia funkcje wykrywania, czy ciąg zawiera znak Emoji:
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 RGBNSUInteger bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(rawData, width, height,
bitsPerComponent, bytesPerRow, colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGContextRelease(context);
BOOL colorPixelFound = NO;
int x = 0;
int y = 0;
while (y < height && !colorPixelFound) {
while (x < width && !colorPixelFound) {
NSUInteger byteIndex = (bytesPerRow * y) + x * bytesPerPixel;
CGFloat red = (CGFloat)rawData[byteIndex];
CGFloat green = (CGFloat)rawData[byteIndex+1];
CGFloat blue = (CGFloat)rawData[byteIndex+2];
CGFloat h, s, b, a;
UIColor *c = [UIColor colorWithRed:red green:green blue:blue alpha:1.0f];
[c getHue:&h saturation:&s brightness:&b alpha:&a];//Note: I wrote this method years ago, can't remember why I check HSB instead of just checking r,g,b==0; Upon further review this step might not be needed, but I haven't tested to confirm yet.
b /= 255.0f;
if (b > 0) {
colorPixelFound = YES;
}
x++;
}
x=0;
y++;
}
return colorPixelFound;
}
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:
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ą:
extensionString{
var isSingleEmoji : Bool {
ifself.count == 1 {
let emodjiGlyphPattern = "\\p{RI}{2}|(\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})|[\\p{Emoji}&&\\p{Other_symbol}])(\\x{200D}(\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})|[\\p{Emoji}&&\\p{Other_symbol}]))*"let fullRange = NSRange(location: 0, length: self.utf16.count)
iflet regex = try? NSRegularExpression(pattern: emodjiGlyphPattern, options: .caseInsensitive) {
let regMatches = regex.matches(in: self, options: NSRegularExpression.MatchingOptions(), range: fullRange)
if regMatches.count > 0 {
// if any range found — it means, that that single character is emojireturntrue
}
}
}
returnfalse
}
}
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.
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.
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:
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”.
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}.
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:
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:
let character = string[string.index(after: string.startIndex)]
lublet secondCharacter = string[string.index(string.startIndex, offsetBy: 1)]
Odpowiedzi:
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:
Ostatni, glif 1️⃣ zawiera trzy znaki Unicode:
1
⃣
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.Scalar
NowyProperty
typ 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.
źródło
containsOnlyEmoji
sprawdzania. Zaktualizowałem również przykład do Swift 3.0.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 } }
źródło
0x1F900...0x1F9FF
(na Wikipedii). Nie jestem pewien, czy cały zakres należy uznać za emoji.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.
źródło
Swift 5.0
… Wprowadził nowy sposób sprawdzenia dokładnie tego!
Musisz złamać
String
w jegoScalars
. KażdyScalar
maProperty
wartość, która wspiera tęisEmoji
wartość!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
isEmojiPresentation
zamiastisEmoji
, ponieważ Apple stwierdza, co następujeisEmoji
: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) } }
źródło
scalar.properties.isEmoji scalar.properties.isEmojiPresentation scalar.properties.isEmojiModifier scalar.properties.isEmojiModifierBase scalar.properties.isJoinControl scalar.properties.isVariationSelector
"6".unicodeScalars.first!.properties.isEmoji
oceni jakotrue
Dzięki Swift 5 możesz teraz sprawdzić właściwości Unicode każdego znaku w ciągu. To daje nam wygodną
isEmoji
zmienną 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ą,
isEmoji
a 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
źródło
Character("3️⃣").isEmoji // true
gdyCharacter("3").isEmoji // false
Swift 3 Uwaga:
Wygląda na to, że
cnui_containsEmojiCharacters
metoda została usunięta lub przeniesiona do innej biblioteki dynamicznej._containsEmoji
powinien 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,
NSString
któ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
AnyObject
przed użyciemvalueForKey
:let str = "hello😊" (str as AnyObject).valueForKey("cnui_containsEmojiCharacters") // 1 (str as AnyObject).valueForKey("_containsEmoji") // 1
Metody znalezione w pliku nagłówkowym NSString .
źródło
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.
źródło
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; }
źródło
AppleColorEmoji
, dodając to teraz jako zabezpieczenie przed awarią, chociaż myślę, że Apple i tak zrobi to domyślnieW 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; }
źródło
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 } } }
źródło
Możesz użyć NSString-RemoveEmoji w ten sposób:
if string.isIncludingEmoji { }
źródło
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:
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:
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:
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:
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}] ) )*
źródło
miałem ten sam problem i skończyło się na zrobieniu rozszerzenia
String
iCharacter
.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,
CharacterSet
który można znaleźć tutaj:https://github.com/piterwilson/StringEmoji
Stałe
niech emojiCharacterSet: CharacterSetZestaw 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
String
instancja reprezentuje znany pojedynczy znak Emoji
var zawieraEmoji: Bool {get}print("".isEmoji) // false print("😁".isEmoji) // true print("😁😜".isEmoji) // false (String is not a single Emoji)
Określa, czy
String
instancja zawiera znany znak Emoji
var unicodeName: String {get}print("".containsEmoji) // false print("😁".containsEmoji) // true print("😁😜".containsEmoji) // true
Stosuje
kCFStringTransformToUnicodeName
-CFStringTransform
na kopii String
var niceUnicodeName: String {get}print("á".unicodeName) // \N{LATIN SMALL LETTER A WITH ACUTE} print("😜".unicodeName) // "\N{FACE WITH STUCK-OUT TONGUE AND WINKING EYE}"
Zwraca wynik
kCFStringTransformToUnicodeName
-CFStringTransform
z usuniętymi\N{
przedrostkami i}
sufiksamiprint("á".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
Character
instancja reprezentuje znany znak Emojiprint("".isEmoji) // false print("😁".isEmoji) // true
źródło