Подтвердить что ты не робот

Выясните, является ли символ в String emoji?

Мне нужно выяснить, является ли символ в строке emoji.

Например, у меня есть этот символ:

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

Мне нужно выяснить, является ли этот символ эмози.

4b9b3361

Ответ 1

Я наткнулся на разницу между символами, скалярами Юникода и глифами.

Например, глиф 👨‍👨‍👧‍👧 состоит из 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

Ответ 2

Самый простой, самый чистый и быстрый способ добиться этого - просто проверить коды кода 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
    }

}

Ответ 3

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

Это мое исправление с обновленными диапазонами.

Ответ 4

Swift 5.0

... представил новый способ проверки именно этого!

Вы должны разбить свой 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)
    }
}

Ответ 5

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.

Ответ 6

Вы можете использовать этот код пример или этот 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"]

Я создал небольшой пример проекта с указанным выше кодом.

Ответ 7

Для 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;
}

Ответ 8

Будущее Доказательство: вручную проверьте пиксели персонажа; другие решения будут ломаться (и ломаться) по мере добавления новых смайликов.

Примечание: это Objective-C (может быть преобразован в Swift)

С годами эти решения по обнаружению смайликов продолжают ломаться, так как 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;

}

Ответ 9

Абсолютно аналогичный ответ на те, что писал до меня, но с обновленным набором скаляров смайликов.

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

}

Ответ 10

С 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

Ответ 11

Вы можете использовать NSString-RemoveEmoji следующим образом:

if string.isIncludingEmoji {

}

Ответ 12

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

Ответ 13

Для Swift 5.0:

Аккаунты для:

  • Смайлики не эмодзи (например, символы ASCII)
  • Столяр нулевой ширины (считайте emo как один смайлик, а не 4)
  • Модификаторы (например, тон кожи 🏽)
  • Селекторы вариаций

Все верно:

"😀".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
    }
}

Ответ 14

У меня была та же проблема и в итоге появились расширения String и Character.

Код слишком длинный, чтобы опубликовать его, так как на самом деле он перечисляет все emojis (из официального списка Unicode v5.0) в CharacterSet, который вы можете найти здесь:

https://github.com/piterwilson/StringEmoji

Константы

let emojiCharacterSet: CharacterSet

Набор символов, содержащий все известные emoji (как описано в официальном Unicode List 5.0 http://unicode.org/emoji/charts-5.0/emoji-list.html)

Строка

var isEmoji: Bool {get}

Независимо от того, представляет ли экземпляр 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

Символ

var isEmoji: Bool {get}

Независимо от того, представляет ли экземпляр Character известный символ Эможи

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