Мне нужно выяснить, является ли символ в строке emoji.
Например, у меня есть этот символ:
let string = "😀"
let character = Array(string)[0]
Мне нужно выяснить, является ли этот символ эмози.
Мне нужно выяснить, является ли символ в строке emoji.
Например, у меня есть этот символ:
let string = "😀"
let character = Array(string)[0]
Мне нужно выяснить, является ли этот символ эмози.
Я наткнулся на разницу между символами, скалярами Юникода и глифами.
Например, глиф 👨👨👧👧 состоит из 7 скаляров Юникода:
Другой пример: глиф 👌🏿 состоит из двух скаляров Юникода:
Поэтому при рендеринге символов получающиеся символы действительно имеют значение.
То, что я искал, было способом определить, является ли строка точно и только одним смайликом. Таким образом, я мог отобразить его больше, чем обычный текст (как сообщения делают на iOS10 и WhatsApp в настоящее время). Как описано выше, количество символов действительно бесполезно. ("Клеевой персонаж" также не считается смайликом).
Что вы можете сделать, так это использовать CoreText, чтобы разбить строку на глифы и сосчитать их. Кроме того, я бы переместил часть расширения, предложенного Арнольдом и Себастьяном Лопесом, в отдельное расширение UnicodeScalar
.
Это дает вам следующий результат:
import Foundation
extension UnicodeScalar {
/// Note: This method is part of Swift 5, so you can omit this.
/// See: https://developer.apple.com/documentation/swift/unicode/scalar
var isEmoji: Bool {
switch value {
case 0x1F600...0x1F64F, // Emoticons
0x1F300...0x1F5FF, // Misc Symbols and Pictographs
0x1F680...0x1F6FF, // Transport and Map
0x1F1E6...0x1F1FF, // Regional country flags
0x2600...0x26FF, // Misc symbols
0x2700...0x27BF, // Dingbats
0xE0020...0xE007F, // Tags
0xFE00...0xFE0F, // Variation Selectors
0x1F900...0x1F9FF, // Supplemental Symbols and Pictographs
0x1F018...0x1F270, // Various asian characters
0x238C...0x2454, // Misc items
0x20D0...0x20FF: // Combining Diacritical Marks for Symbols
return true
default: return false
}
}
var isZeroWidthJoiner: Bool {
return value == 8205
}
}
extension String {
// Not needed anymore in swift 4.2 and later, using '.count' will give you the correct result
var glyphCount: Int {
let richText = NSAttributedString(string: self)
let line = CTLineCreateWithAttributedString(richText)
return CTLineGetGlyphCount(line)
}
var isSingleEmoji: Bool {
return glyphCount == 1 && containsEmoji
}
var containsEmoji: Bool {
return unicodeScalars.contains { $0.isEmoji }
}
var containsOnlyEmoji: Bool {
return !isEmpty
&& !unicodeScalars.contains(where: {
!$0.isEmoji && !$0.isZeroWidthJoiner
})
}
// The next tricks are mostly to demonstrate how tricky it can be to determine emoji's
// If anyone has suggestions how to improve this, please let me know
var emojiString: String {
return emojiScalars.map { String($0) }.reduce("", +)
}
var emojis: [String] {
var scalars: [[UnicodeScalar]] = []
var currentScalarSet: [UnicodeScalar] = []
var previousScalar: UnicodeScalar?
for scalar in emojiScalars {
if let prev = previousScalar, !prev.isZeroWidthJoiner, !scalar.isZeroWidthJoiner {
scalars.append(currentScalarSet)
currentScalarSet = []
}
currentScalarSet.append(scalar)
previousScalar = scalar
}
scalars.append(currentScalarSet)
return scalars.map { $0.map { String($0) }.reduce("", +) }
}
fileprivate var emojiScalars: [UnicodeScalar] {
var chars: [UnicodeScalar] = []
var previous: UnicodeScalar?
for cur in unicodeScalars {
if let previous = previous, previous.isZeroWidthJoiner, cur.isEmoji {
chars.append(previous)
chars.append(cur)
} else if cur.isEmoji {
chars.append(cur)
}
previous = cur
}
return chars
}
}
Что даст вам следующие результаты:
"👌🏿".isSingleEmoji // true
"🙎🏼♂️".isSingleEmoji // true
"👨👩👧👧".isSingleEmoji // true
"👨👩👧👧".containsOnlyEmoji // true
"Hello 👨👩👧👧".containsOnlyEmoji // false
"Hello 👨👩👧👧".containsEmoji // true
"👫 Héllo 👨👩👧👧".emojiString // "👫👨👩👧👧"
"👨👩👧👧".glyphCount // 1
"👨👩👧👧".characters.count // 4, Will return '1' in Swift 4.2 so previous method not needed anymore
"👨👩👧👧".count // 4, Will return '1' in Swift 4.2 so previous method not needed anymore
"👫 Héllœ 👨👩👧👧".emojiScalars // [128107, 128104, 8205, 128105, 8205, 128103, 8205, 128103]
"👫 Héllœ 👨👩👧👧".emojis // ["👫", "👨👩👧👧"]
"👫👨👩👧👧👨👨👦".isSingleEmoji // false
"👫👨👩👧👧👨👨👦".containsOnlyEmoji // true
"👫👨👩👧👧👨👨👦".glyphCount // 3
"👫👨👩👧👧👨👨👦".characters.count // 8, Will return '3' in Swift 4.2 so previous method not needed anymore
Самый простой, самый чистый и быстрый способ добиться этого - просто проверить коды кода Unicode для каждого символа в строке с известными диапазонами emoji и dingbats, например:
extension String {
var containsEmoji: Bool {
for scalar in unicodeScalars {
switch scalar.value {
case 0x1F600...0x1F64F, // Emoticons
0x1F300...0x1F5FF, // Misc Symbols and Pictographs
0x1F680...0x1F6FF, // Transport and Map
0x2600...0x26FF, // Misc symbols
0x2700...0x27BF, // Dingbats
0xFE00...0xFE0F, // Variation Selectors
0x1F900...0x1F9FF, // Supplemental Symbols and Pictographs
0x1F1E6...0x1F1FF: // Flags
return true
default:
continue
}
}
return false
}
}
extension String {
func containsEmoji() -> Bool {
for scalar in unicodeScalars {
switch scalar.value {
case 0x3030, 0x00AE, 0x00A9,// Special Characters
0x1D000...0x1F77F, // Emoticons
0x2100...0x27BF, // Misc symbols and Dingbats
0xFE00...0xFE0F, // Variation Selectors
0x1F900...0x1F9FF: // Supplemental Symbols and Pictographs
return true
default:
continue
}
}
return false
}
}
Это мое исправление с обновленными диапазонами.
... представил новый способ проверки именно этого!
Вы должны разбить свой String
на его Scalars
. Каждый Scalar
имеет значение Property
, которое поддерживает значение isEmoji
!
На самом деле вы даже можете проверить, является ли Скаляр модификатором Emoji или более. Ознакомьтесь с документацией Apple: https://developer.apple.com/documentation/swift/unicode/scalar/properties
Вы можете рассмотреть возможность проверки isEmojiPresentation
вместо isEmoji
, потому что Apple заявляет следующее для isEmoji
:
Это свойство верно для скаляров, которые по умолчанию отображаются как смайлики, а также для скаляров, которые имеют рендеринг смайликов не по умолчанию, за которым следует U + FE0F VARIATION SELECTOR-16. Это включает в себя некоторые скаляры, которые обычно не считаются смайликами.
Таким образом, на самом деле Emoji разделяется на все модификаторы, но с ним проще работать. И поскольку Swift теперь считает Emoji с модификаторами (например, 👨👩👧👦, 👨🏻💻, 🏴) как 1, вы можете делать все что угодно.
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 указывает на интересный способ получить все Emoji's:
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)
}
}
Swift 3 Примечание:
Похоже, что метод cnui_containsEmojiCharacters
был удален или перемещен в другую динамическую библиотеку. _containsEmoji
должен по-прежнему работать.
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:
Недавно я обнаружил частный API на NSString
, который предоставляет функциональные возможности для обнаружения, если строка содержит символ Emoji:
let str: NSString = "hello😊"
С протоколом objc и unsafeBitCast
:
@objc protocol NSStringPrivate {
func cnui_containsEmojiCharacters() -> ObjCBool
func _containsEmoji() -> ObjCBool
}
let strPrivate = unsafeBitCast(str, NSStringPrivate.self)
strPrivate.cnui_containsEmojiCharacters() // true
strPrivate._containsEmoji() // true
С valueForKey
:
str.valueForKey("cnui_containsEmojiCharacters") // 1
str.valueForKey("_containsEmoji") // 1
С чистой строкой Swift вы должны передать строку как AnyObject
перед использованием valueForKey
:
let str = "hello😊"
(str as AnyObject).valueForKey("cnui_containsEmojiCharacters") // 1
(str as AnyObject).valueForKey("_containsEmoji") // 1
Методы, найденные в файл заголовка NSString.
Вы можете использовать этот код пример или этот pod.
Чтобы использовать его в Swift, импортируйте категорию в YourProject_Bridging_Header
#import "NSString+EMOEmoji.h"
Затем вы можете проверить диапазон для каждого emoji в своей строке:
let example: NSString = "string👨👨👧👧with😍emojis✊🏿" //string with emojis
let containsEmoji: Bool = example.emo_containsEmoji()
print(containsEmoji)
// Output: ["true"]
Для Swift 3.0.2 следующий ответ является самым простым:
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;
}
С годами эти решения по обнаружению смайликов продолжают ломаться, так как Apple добавляет новые смайлики с новыми методами (например, смоделированные с помощью кожи смайлики, созданные путем предварительного проклятия персонажа дополнительным персонажем) и т.д.
Я наконец сломался и просто написал следующий метод, который работает для всех текущих смайликов и должен работать для всех будущих смайликов.
Решение создает UILabel с символом и черным фоном. Затем CG делает снимок метки, и я сканирую все пиксели в снимке на наличие любых не сплошных черных пикселей. Причина, по которой я добавляю черный фон, состоит в том, чтобы избежать проблем с ложным окрашиванием из-за субпиксельного рендеринга.
Решение работает ОЧЕНЬ быстро на моем устройстве, я могу проверять сотни символов в секунду, но следует отметить, что это решение CoreGraphics и его не следует использовать интенсивно, как при обычном текстовом методе. Обработка графики требует больших объемов данных, поэтому одновременная проверка тысяч символов может привести к заметному отставанию.
-(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;
}
Абсолютно аналогичный ответ на те, что писал до меня, но с обновленным набором скаляров смайликов.
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
}
}
}
С Swift 5 теперь вы можете проверить свойства юникода каждого символа в вашей строке. Это дает нам удобную переменную isEmoji
для каждой буквы. Проблема в том, что isEmoji
вернет true для любого символа, который может быть преобразован в 2-байтовый эмодзи, например 0-9.
Мы можем посмотреть на переменную isEmoji
, а также проверить наличие модификатора смайликов, чтобы определить, будут ли неоднозначные символы отображаться как смайлики.
Это решение должно быть гораздо более перспективным, чем предлагаемые здесь регулярные выражения.
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)
}
}
Давать нам
"hey".containsEmoji() //false
"Hello World 😎".containsEmoji() //true
"Hello World 😎".containsOnlyEmojis() //false
"😎".containsEmoji() //true
"😎".containsOnlyEmojis() //true
Вы можете использовать NSString-RemoveEmoji следующим образом:
if string.isIncludingEmoji {
}
- (BOOL)isSingleEmoji:(NSString *)character {
if (!character.length || character.length > 2) {
return NO;
}
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:character];
CTLineRef line = CTLineCreateWithAttributedString((CFAttributedStringRef)attrString);
NSInteger glyphCount = CTLineGetGlyphCount(line);
return glyphCount == 1;
}
Аккаунты для:
Все верно:
"😀".isPureEmojiString(withMinLength: 1, max: 1) // 1 scalar
"👌🏾".isPureEmojiString(withMinLength: 1, max: 1) // 2 scalars (emoji modifier)
"⛄️".isPureEmojiString(withMinLength: 1, max: 1) // 2 scalars (variation selector)
"🇵🇷".isPureEmojiString(withMinLength: 1, max: 1) // 2 scalars (2x regional indicators)
"🧖♀️".isPureEmojiString(withMinLength: 1, max: 1) // 4 scalars (ZWJ + ♀ + variation)
"👨👩👦👦".isPureEmojiString(withMinLength: 1, max: 1) // 7 scalars (ZW joiners)
extension String {
func isPureEmojiString(withMinLength min: Int, max: Int) -> Bool {
if count < min || count > max {
return false
}
return isPureEmojiString()
}
func isPureEmojiString() -> Bool {
for scalar in unicodeScalars {
let prop = scalar.properties
if prop.isJoinControl || prop.isVariationSelector || prop.isEmojiModifier {
continue
}
else if scalar.properties.isEmoji == false || scalar.isASCII == true {
return false
}
}
return true
}
}
У меня была та же проблема и в итоге появились расширения String
и Character
.
Код слишком длинный, чтобы опубликовать его, так как на самом деле он перечисляет все emojis (из официального списка Unicode v5.0) в CharacterSet
, который вы можете найти здесь:
https://github.com/piterwilson/StringEmoji
Набор символов, содержащий все известные emoji (как описано в официальном Unicode List 5.0 http://unicode.org/emoji/charts-5.0/emoji-list.html)
Независимо от того, представляет ли экземпляр String
известный один символ Эможи
print("".isEmoji) // false
print("😁".isEmoji) // true
print("😁😜".isEmoji) // false (String is not a single Emoji)
var содержитEmoji: Bool {get}
Будет ли экземпляр String
содержать известный символ Эможи
print("".containsEmoji) // false
print("😁".containsEmoji) // true
print("😁😜".containsEmoji) // true
var unicodeName: String {get}
Применяет a kCFStringTransformToUnicodeName
- CFStringTransform
на копии строки
print("á".unicodeName) // \N{LATIN SMALL LETTER A WITH ACUTE}
print("😜".unicodeName) // "\N{FACE WITH STUCK-OUT TONGUE AND WINKING EYE}"
var niceUnicodeName: String {get}
Возвращает результат kCFStringTransformToUnicodeName
- CFStringTransform
с префиксами \N{
и }
удалены суффиксы
print("á".unicodeName) // LATIN SMALL LETTER A WITH ACUTE
print("😜".unicodeName) // FACE WITH STUCK-OUT TONGUE AND WINKING EYE
Независимо от того, представляет ли экземпляр Character
известный символ Эможи
print("".isEmoji) // false
print("😁".isEmoji) // true