web-dev-qa-db-ja.com

文字列の文字が絵文字であるかどうかを確認しますか?

文字列の文字が絵文字かどうかを調べる必要があります。

たとえば、私はこのキャラクターを持っています:

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

そのキャラクターが絵文字かどうか調べる必要があります。

63
Andrew

私がつまずいたのは、文字、ユニコ​​ードスカラー、グリフの違いです。

たとえば、グリフ???? ‍ ???? ‍ ???? ‍ ???? 7つのUnicodeスカラーで構成されます。

  • 4つの絵文字:????????????????
  • 各絵文字の間に特殊文字があり、これは文字のりのように機能します。 詳細情報の仕様 を参照してください

別の例、グリフ???????? 2つのUnicodeスカラーで構成されます。

  • 通常の絵文字:????
  • スキントーン修飾子:????

したがって、文字をレンダリングするとき、結果のグリフは本当に重要です。

私が探していたのは、文字列が正確かつ唯一の絵文字かどうかを検出する方法でした。そのため、通常のテキストよりも大きくレンダリングすることができます(iOS10でのメッセージや、最近のWhatsAppのように)。上記のように、文字カウントは実際には役に立ちません。 (「接着文字」も絵文字とは見なされません)。

できることは、CoreTextを使用して、文字列をグリフに分解してカウントするのを支援することです。さらに、ArnoldとSebastian Lopezによって提案された拡張の一部を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
148
Kevin R

これを達成するための最も簡単でクリーンなswiftiestの方法は、次のように、文字列内の各文字のUnicodeコードポイントを既知の絵文字と絵文字の範囲に対して単純にチェックすることです。

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
    }

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

これは私の修正であり、範囲が更新されています。

8
Sebastian Lopez

Swift 5.0

…これを正確にチェックする新しい方法を導入しました!

StringScalars に分割する必要があります。各Scalarには Property 値があり、これは isEmoji 値をサポートしています!

実際、スカラーが絵文字修飾子であるかどうかを確認することもできます。 Appleのドキュメントを確認してください: https://developer.Apple.com/documentation/Swift/unicode/scalar/properties

AppleはisEmojiPresentationについて次のように述べているため、isEmojiの代わりにisEmojiをチェックすることを検討してください。

このプロパティは、デフォルトで絵文字としてレンダリングされるスカラー、およびその後にU + FE0F VARIATION SELECTOR-16が続く場合にデフォルト以外の絵文字レンダリングを行うスカラーに対しても当てはまります。これには、通常絵文字とは見なされないスカラーが含まれます。


この方法では、実際には絵文字がすべての修飾子に分割されますが、処理ははるかに簡単です。そして、Swiftは修飾子付きの絵文字をカウントするようになりました(例:???? ‍ ???? ‍ ???? ‍ ????、???????? ‍? ???、????)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 は、すべての絵文字を取得する興味深い方法を示しています。

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
alexkaessner

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:

私は最近、文字列に絵文字が含まれているかどうかを検出する機能を公開するNSStringのプライベートAPIを発見しました。

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ヘッダーファイル にあるメソッド。

4
JAL

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;
}
2
Ankit Goyal

私の前に書いたものとまったく同じ答えですが、絵文字スカラーの更新されたセットがあります。

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

}
2

将来の証明:キャラクターのピクセルを手動で確認します。新しい絵文字が追加されると、他のソリューションは壊れます(壊れてしまいます)。

注:これはObjective-Cです(Swiftに変換できます)

これらの絵文字検出ソリューションは、長年にわたってAppleが新しい絵文字を追加し、新しいメソッドを追加します(追加の文字で文字を事前にカーリングすることで構築されたスキントーンの絵文字など)。

私はついに故障し、現在のすべての絵文字で機能し、将来のすべての絵文字で機能する次のメソッドを作成しました。

ソリューションは、文字と黒の背景を持つUILabelを作成します。次に、CGはラベルのスナップショットを取得し、スナップショット内のすべてのピクセルをスキャンして、黒でないピクセルを探します。黒の背景を追加する理由は、 サブピクセルレンダリング による偽色の問題を回避するためです。

このソリューションは私のデバイス上で非常に高速に実行されます。1秒間に数百の文字をチェックできますが、これは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;

}
2
Albert Renshaw

このコード example またはこの pod を使用できます。

Swiftで使用するには、カテゴリをYourProject_Bridging_Header

#import "NSString+EMOEmoji.h"

次に、文字列内のすべての絵文字の範囲を確認できます。

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

let containsEmoji: Bool = example.Emo_containsEmoji()

    print(containsEmoji)

// Output: ["true"]

上記のコードを使用して小さなサンプルプロジェクトを作成しました。

2
Gabriel.Massana

NSString-RemoveEmoji は次のように使用できます。

if string.isIncludingEmoji {

}
1
Shardul

Swift 5を使用すると、文字列の各文字のUnicodeプロパティを検査できます。これにより、各文字に便利なisEmoji変数が与えられます。問題はisEmoji 0〜9など、2バイトの絵文字に変換できるすべての文字に対してtrueを返します。

変数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
1
Miniroo

Swift 5.0:

のアカウント:

  • 絵文字以外の絵文字(例ASCII文字)
  • 幅ゼロのジョイナー(4つではなく、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
    }
}
0
beebcon