文字列の文字が絵文字かどうかを調べる必要があります。
たとえば、私はこのキャラクターを持っています:
let string = "????"
let character = Array(string)[0]
そのキャラクターが絵文字かどうか調べる必要があります。
私がつまずいたのは、文字、ユニコードスカラー、グリフの違いです。
たとえば、グリフ???? ???? ???? ???? 7つのUnicodeスカラーで構成されます。
別の例、グリフ???????? 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
これを達成するための最も簡単でクリーンな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
}
}
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
値をサポートしています!
実際、スカラーが絵文字修飾子であるかどうかを確認することもできます。 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)
}
}
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ヘッダーファイル にあるメソッド。
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;
}
私の前に書いたものとまったく同じ答えですが、絵文字スカラーの更新されたセットがあります。
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
}
}
}
これらの絵文字検出ソリューションは、長年にわたって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;
}
このコード 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"]
NSString-RemoveEmoji は次のように使用できます。
if string.isIncludingEmoji {
}
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
のアカウント:
すべて真実:
"????".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
}
}