我有一个 UITextView,我需要检测用户是否输入了表情符号字符。
我认为只检查最新字符的 unicode 值就足够了,但是对于新的 emoji 2s,一些字符分散在整个 unicode 索引中(即 Apple 新设计的版权和注册标志)。
也许与使用 NSLocale 或 LocalizedString 值检查字符的语言有关?
有谁知道一个好的解决方案?
谢谢!
我有一个 UITextView,我需要检测用户是否输入了表情符号字符。
我认为只检查最新字符的 unicode 值就足够了,但是对于新的 emoji 2s,一些字符分散在整个 unicode 索引中(即 Apple 新设计的版权和注册标志)。
也许与使用 NSLocale 或 LocalizedString 值检查字符的语言有关?
有谁知道一个好的解决方案?
谢谢!
多年来,随着 Apple 添加带有新方法的新表情符号(例如通过预先诅咒一个角色和一个额外角色构建的肤色表情符号)等,这些表情符号检测解决方案不断出现问题。
我终于崩溃了,只写了以下适用于所有当前表情符号的方法,并且应该适用于所有未来的表情符号。
该解决方案创建一个带有字符和黑色背景的 UILabel。然后 CG 拍摄标签的快照,我扫描快照中的所有像素以查找任何非纯黑色像素。我添加黑色背景的原因是为了避免由于亚像素渲染而导致的假着色问题
该解决方案在我的设备上运行得非常快,我可以每秒检查数百个字符,但应该注意这是一个 CoreGraphics 解决方案,不应该像使用常规文本方法那样大量使用。图形处理的数据量很大,因此一次检查数千个字符可能会导致明显的延迟。
-(BOOL)isEmoji:(NSString *)character {
UILabel *characterRender = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 1, 1)];
characterRender.text = character;
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;
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];
b /= 255.0f;
if (b > 0) {
colorPixelFound = YES;
}
x++;
}
x=0;
y++;
}
return colorPixelFound;
}
首先让我们谈谈您的“55357 方法” ——以及为什么它适用于许多表情符号字符。
在 Cocoa 中,anNSString
是unichar
s 的集合,并且unichar
只是一个类型别名,unsigned short
与UInt16
. UInt16
由于is的最大值0xffff
,这排除了相当多的 emoji 表情能够放入 one unichar
,因为用于表情符号的六个主要 Unicode 块中只有两个属于这个范围:
这些块包含 113 个表情符号,另外 66 个可以表示为单个的表情符号unichar
散布在其他各种块中。但是,这 179 个字符仅代表1126 个 emoji 基本字符的一小部分,其余的必须由多个unichar
.
让我们分析一下您的代码:
unichar unicodevalue = [text characterAtIndex:0];
发生的情况是您只是获取unichar
字符串的第一个,虽然这适用于前面提到的 179 个字符,但当您遇到 UTF-32 字符时它会分解,因为NSString
将所有内容都转换为 UTF-16 编码。通过用代理对替换 UTF-32 值来进行转换,这意味着NSString
now 包含两个unichar
s。
现在我们来了解为什么数字 55357 或出现0xd83d
在许多表情符号中:当您只查看 UTF-32 字符的第一个 UTF-16 值时,您会得到高代理项,每个代理项的跨度为 1024低代理。高位代理的范围0xd83d
是 U+1F400–U+1F7FF,从最大的表情符号块、杂项符号和象形文字(U+1F300–U+1F5FF) 的中间开始,一直延伸到几何形状扩展(U+1F780–U+1F7FF) – 在这个范围内总共包含 563 个表情符号和 333 个非表情符号字符。
因此,令人印象深刻的 50% 的 emoji 基本字符具有高代理0xd83d
,但这些扣除方法仍然留下 384 个未处理的 emoji 字符,并且至少给出同样多的误报。
我最近用 Swift 实现回答了一个有点相关的问题,如果你愿意,你可以看看在这个框架中是如何检测表情符号的,我创建这个框架的目的是用自定义图像替换标准表情符号。
无论如何,您可以做的是从字符中提取 UTF-32 代码点,我们将根据规范执行此操作:
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
// Get the UTF-16 representation of the text.
unsigned long length = text.length;
unichar buffer[length];
[text getCharacters:buffer];
// Initialize array to hold our UTF-32 values.
NSMutableArray *array = [[NSMutableArray alloc] init];
// Temporary stores for the UTF-32 and UTF-16 values.
UTF32Char utf32 = 0;
UTF16Char h16 = 0, l16 = 0;
for (int i = 0; i < length; i++) {
unichar surrogate = buffer[i];
// High surrogate.
if (0xd800 <= surrogate && surrogate <= 0xd83f) {
h16 = surrogate;
continue;
}
// Low surrogate.
else if (0xdc00 <= surrogate && surrogate <= 0xdfff) {
l16 = surrogate;
// Convert surrogate pair to UTF-32 encoding.
utf32 = ((h16 - 0xd800) << 10) + (l16 - 0xdc00) + 0x10000;
}
// Normal UTF-16.
else {
utf32 = surrogate;
}
// Add UTF-32 value to array.
[array addObject:[NSNumber numberWithUnsignedInteger:utf32]];
}
NSLog(@"%@ contains values:", text);
for (int i = 0; i < array.count; i++) {
UTF32Char character = (UTF32Char)[[array objectAtIndex:i] unsignedIntegerValue];
NSLog(@"\t- U+%x", character);
}
return YES;
}
键入“”到UITextView
控制台中:
contains values:
- U+1f60e
使用这种逻辑,只需将 的值character
与您的表情符号代码点的数据源进行比较,您就会确切地知道该字符是否是表情符号。
附言
还有一些“不可见”的字符,即Variation Selectors和zero-width joiners,也应该被处理,所以我建议研究这些字符以了解它们的行为方式。
另一种解决方案:https ://github.com/woxtu/NSString-RemoveEmoji
然后,导入此扩展后,您可以像这样使用它:
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
// Detect if an Emoji is in the string "text"
if(text.isIncludingEmoji) {
// Show an UIAlertView, or whatever you want here
return NO;
}
return YES;
}
希望有帮助;)
如果您不希望键盘显示表情符号,您可以使用
YOURTEXTFIELD/YOURTEXTVIEW.keyboardType = .ASCIICapable
这将显示没有表情符号的键盘
这是Swift中的表情符号检测方法。它工作正常。希望它会帮助别人。
func isEmoji(_ character: String?) -> Bool {
if character == "" || character == "\n" {
return false
}
let characterRender = UILabel(frame: CGRect(x: 0, y: 0, width: 1, height: 1))
characterRender.text = character
characterRender.backgroundColor = UIColor.black
characterRender.sizeToFit()
let rect: CGRect = characterRender.bounds
UIGraphicsBeginImageContextWithOptions(rect.size, true, 0.0)
if let contextSnap:CGContext = UIGraphicsGetCurrentContext() {
characterRender.layer.render(in: contextSnap)
}
let capturedImage: UIImage? = (UIGraphicsGetImageFromCurrentImageContext())
UIGraphicsEndImageContext()
var colorPixelFound:Bool = false
let imageRef = capturedImage?.cgImage
let width:Int = imageRef!.width
let height:Int = imageRef!.height
let colorSpace = CGColorSpaceCreateDeviceRGB()
let rawData = calloc(width * height * 4, MemoryLayout<CUnsignedChar>.stride).assumingMemoryBound(to: CUnsignedChar.self)
let bytesPerPixel:Int = 4
let bytesPerRow:Int = bytesPerPixel * width
let bitsPerComponent:Int = 8
let context = CGContext(data: rawData, width: Int(width), height: Int(height), bitsPerComponent: Int(bitsPerComponent), bytesPerRow: Int(bytesPerRow), space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue)
context?.draw(imageRef!, in: CGRect(x: 0, y: 0, width: width, height: height))
var x:Int = 0
var y:Int = 0
while (y < height && !colorPixelFound) {
while (x < width && !colorPixelFound) {
let byteIndex: UInt = UInt((bytesPerRow * y) + x * bytesPerPixel)
let red = CGFloat(rawData[Int(byteIndex)])
let green = CGFloat(rawData[Int(byteIndex+1)])
let blue = CGFloat(rawData[Int(byteIndex + 2)])
var h: CGFloat = 0.0
var s: CGFloat = 0.0
var b: CGFloat = 0.0
var a: CGFloat = 0.0
var c = UIColor(red:red, green:green, blue:blue, alpha:1.0)
c.getHue(&h, saturation: &s, brightness: &b, alpha: &a)
b = b/255.0
if Double(b) > 0.0 {
colorPixelFound = true
}
x+=1
}
x=0
y+=1
}
return colorPixelFound
}
以下是检查绘制的字符是否具有任何颜色的代码的更清洁和更有效的实现。
这些已被编写为类别/扩展方法,以使它们更易于使用。
目标-C:
NSString+表情符号.h:
#import <Foundation/Foundation.h>
@interface NSString (Emoji)
- (BOOL)hasColor;
@end
NSString+表情符号.m:
#import "NSString+Emoji.h"
#import <UIKit/UIKit.h>
@implementation NSString (Emoji)
- (BOOL)hasColor {
UILabel *characterRender = [[UILabel alloc] initWithFrame:CGRectZero];
characterRender.text = self;
characterRender.textColor = UIColor.blackColor;
characterRender.backgroundColor = UIColor.blackColor;//needed to remove subpixel rendering colors
[characterRender sizeToFit];
CGRect rect = characterRender.bounds;
UIGraphicsBeginImageContextWithOptions(rect.size, YES, 1);
CGContextRef contextSnap = UIGraphicsGetCurrentContext();
[characterRender.layer renderInContext:contextSnap];
UIImage *capturedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGImageRef imageRef = capturedImage.CGImage;
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
size_t bytesPerPixel = 4;
size_t bitsPerComponent = 8;
size_t bytesPerRow = bytesPerPixel * width;
size_t size = height * width * bytesPerPixel;
unsigned char *rawData = (unsigned char *)calloc(size, sizeof(unsigned char));
CGContextRef context = CGBitmapContextCreate(rawData, width, height,
bitsPerComponent, bytesPerRow, colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGContextRelease(context);
BOOL result = NO;
for (size_t offset = 0; offset < size; offset += bytesPerPixel) {
unsigned char r = rawData[offset];
unsigned char g = rawData[offset+1];
unsigned char b = rawData[offset+2];
if (r || g || b) {
result = YES;
break;
}
}
free(rawData);
return result;
}
@end
示例用法:
if ([@"" hasColor]) {
// Yes, it does
}
if ([@"@" hasColor]) {
} else {
// No, it does not
}
迅速:
字符串+表情符号.swift:
import UIKit
extension String {
func hasColor() -> Bool {
let characterRender = UILabel(frame: .zero)
characterRender.text = self
characterRender.textColor = .black
characterRender.backgroundColor = .black
characterRender.sizeToFit()
let rect = characterRender.bounds
UIGraphicsBeginImageContextWithOptions(rect.size, true, 1)
let contextSnap = UIGraphicsGetCurrentContext()!
characterRender.layer.render(in: contextSnap)
let capturedImageTmp = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
guard let capturedImage = capturedImageTmp else { return false }
let imageRef = capturedImage.cgImage!
let width = imageRef.width
let height = imageRef.height
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bytesPerPixel = 4
let bytesPerRow = bytesPerPixel * width
let bitsPerComponent = 8
let size = width * height * bytesPerPixel
let rawData = calloc(size, MemoryLayout<CUnsignedChar>.stride).assumingMemoryBound(to: CUnsignedChar.self)
guard let context = CGContext(data: rawData, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue) else { return false }
context.draw(imageRef, in: CGRect(x: 0, y: 0, width: width, height: height))
var result = false
for offset in stride(from: 0, to: size, by: 4) {
let r = rawData[offset]
let g = rawData[offset + 1]
let b = rawData[offset + 2]
if (r > 0 || g > 0 || b > 0) {
result = true
break
}
}
free(rawData)
return result
}
}
示例用法:
if "".hasColor() {
// Yes, it does
}
if "@".hasColor() {
} else {
// No, it does not
}
Swift 的 String 类型有一个属性 .isEmoji
最好检查 isEmojiPresentation 警告的文档
https://developer.apple.com/documentation/swift/unicode/scalar/properties/3081577-isemoji
好吧,您可以使用以下方法检测它是否只有 ascii 字符:
[myString canBeConvertedToEncoding:NSASCIIStringEncoding];
如果失败(或有表情符号),它会拒绝。然后你可以做一个 if else 语句,不允许他们点击进入或其他东西。
表情符号字符长度为 2,因此在 shouldChangeTextInRange: 方法中检查字符串长度是否为 2:在键盘击中每个键后调用
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
// Detect if an Emoji is in the string "text"
if([text length]==2) {
// Show an UIAlertView, or whatever you want here
return YES;
}
else
{
return NO;
}
}