MLEmojiLabel.m 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728
  1. //
  2. // MLEmojiLabel.m
  3. // MLEmojiLabel
  4. //
  5. // Created by molon on 5/19/14.
  6. // Copyright (c) 2014 molon. All rights reserved.
  7. //
  8. #import "MLEmojiLabel.h"
  9. #pragma mark - 正则列表
  10. #define REGULAREXPRESSION_OPTION(regularExpression,regex,option) \
  11. \
  12. static inline NSRegularExpression * k##regularExpression() { \
  13. static NSRegularExpression *_##regularExpression = nil; \
  14. static dispatch_once_t onceToken; \
  15. dispatch_once(&onceToken, ^{ \
  16. _##regularExpression = [[NSRegularExpression alloc] initWithPattern:(regex) options:(option) error:nil];\
  17. });\
  18. \
  19. return _##regularExpression;\
  20. }\
  21. #define REGULAREXPRESSION(regularExpression,regex) REGULAREXPRESSION_OPTION(regularExpression,regex,NSRegularExpressionCaseInsensitive)
  22. //REGULAREXPRESSION(URLRegularExpression,@"((http[s]{0,1}|ftp)://[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)|(www.[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)|(((http[s]{0,1}|ftp)://|)((?:(?:25[0-5]|2[0-4]\\d|((1\\d{2})|([1-9]?\\d)))\\.){3}(?:25[0-5]|2[0-4]\\d|((1\\d{2})|([1-9]?\\d))))(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)")
  23. REGULAREXPRESSION(URLRegularExpression,@"((http[s]{0,1}|ftp)://[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)|(www.[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)|(((http[s]{0,1}|ftp)://|)((?:(?:25[0-5]|2[0-4]\\d|((1\\d{2})|([1-9]?\\d)))\\.){3}(?:25[0-5]|2[0-4]\\d|((1\\d{2})|([1-9]?\\d))))(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)")
  24. //REGULAREXPRESSION(URLRegularExpression,@"/((https|http|ftp|rtsp|mms)?://)?(([0-9a-z_!~*'().&=+$%-]+: )?[0-9a-z_!~*'().&=+$%-]+@)?(([0-9]{1,3}.){3}[0-9]{1,3}|([0-9a-z_!~*'()-]+.)*([0-9a-z][0-9a-z-]{0,61})?[0-9a-z].[a-z]{2,6})(:[0-9]{1,4})?(((/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+/?)|(/?))/gi")
  25. //REGULAREXPRESSION(PhoneNumerRegularExpression, @"\\d{3}-\\d{8}|\\d{3}-\\d{7}|\\d{4}-\\d{8}|\\d{4}-\\d{7}|1+[3578]+\\d{9}|\\d{8}|\\d{7}")
  26. //REGULAREXPRESSION(EmailRegularExpression, @"[A-Z0-9a-z\\._%+-]+@([A-Za-z0-9-]+\\.)+[A-Za-z]{2,4}")
  27. //REGULAREXPRESSION(AtRegularExpression, @"@[\\u4e00-\\u9fa5\\w\\-]+")
  28. //REGULAREXPRESSION(RobotRegularExpression, @"[0-9]+[:]+[0-9a-zA-Z\\u4e00-\\u9fa5]+\n")
  29. REGULAREXPRESSION(RobotRegularExpression, @"[0-9]{1,2}:[^\\n]*\\n")
  30. //@"#([^\\#|.]+)#"
  31. //REGULAREXPRESSION_OPTION(PoundSignRegularExpression, @"#([\\u4e00-\\u9fa5\\w\\-]+)#", NSRegularExpressionCaseInsensitive)
  32. //微信的表情符其实不是这种格式,这个格式的只是让人看起来更友好。。
  33. //REGULAREXPRESSION(EmojiRegularExpression, @"\\[[a-zA-Z0-9\\u4e00-\\u9fa5]+\\]")
  34. //@"/:[\\w:~!@$&*()|+<>',?-]{1,8}" , // @"/:[\\x21-\\x2E\\x30-\\x7E]{1,8}" ,经过检测发现\w会匹配中文,好奇葩。
  35. REGULAREXPRESSION(SlashEmojiRegularExpression, @"/:[\\x21-\\x2E\\x30-\\x7E]{1,8}")
  36. const CGFloat kLineSpacing = 4.0;
  37. const CGFloat kAscentDescentScale = 0.25; //在这里的话无意义,高度的结局都是和宽度一样
  38. const CGFloat kEmojiWidthRatioWithLineHeight = 1.15;//和字体高度的比例
  39. const CGFloat kEmojiOriginYOffsetRatioWithLineHeight = 0.10; //表情绘制的y坐标矫正值,和字体高度的比例,越大越往下
  40. NSString *const kCustomGlyphAttributeImageName = @"CustomGlyphAttributeImageName";
  41. #define kEmojiReplaceCharacter @"\uFFFC"
  42. #define kURLActionCount 5
  43. NSString * const kURLActions[] = {@"url->",@"email->",@"phoneNumber->",@"at->",@"poundSign->"};
  44. /**
  45. * 搞个管理器,否则自定义plist的话,每个label都会有个副本很操蛋
  46. */
  47. @interface MLEmojiLabelRegexPlistManager : NSObject
  48. - (NSDictionary*)emojiDictForKey:(NSString*)key;
  49. @property (nonatomic, strong) NSMutableDictionary *emojiDictRecords;
  50. @property (nonatomic, strong) NSMutableDictionary *emojiRegularExpressions;
  51. @end
  52. @implementation MLEmojiLabelRegexPlistManager
  53. + (instancetype)sharedInstance {
  54. static MLEmojiLabelRegexPlistManager *_sharedInstance = nil;
  55. static dispatch_once_t onceToken;
  56. dispatch_once(&onceToken, ^{
  57. _sharedInstance = [[[self class] alloc]init];
  58. });
  59. return _sharedInstance;
  60. }
  61. #pragma mark - getter
  62. - (NSMutableDictionary *)emojiDictRecords
  63. {
  64. if (!_emojiDictRecords) {
  65. _emojiDictRecords = [NSMutableDictionary new];
  66. }
  67. return _emojiDictRecords;
  68. }
  69. - (NSMutableDictionary *)emojiRegularExpressions
  70. {
  71. if (!_emojiRegularExpressions) {
  72. _emojiRegularExpressions = [NSMutableDictionary new];
  73. }
  74. return _emojiRegularExpressions;
  75. }
  76. #pragma mark - common
  77. - (NSDictionary*)emojiDictForKey:(NSString*)key
  78. {
  79. NSAssert(key&&key.length>0, @"emojiDictForKey:参数不得为空");
  80. if (self.emojiDictRecords[key]) {
  81. return self.emojiDictRecords[key];
  82. }
  83. NSString *emojiFilePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:key];
  84. NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:emojiFilePath];
  85. NSAssert(dict,@"表情字典%@找不到",key);
  86. self.emojiDictRecords[key] = dict;
  87. return self.emojiDictRecords[key];
  88. }
  89. - (NSRegularExpression *)regularExpressionForRegex:(NSString*)regex
  90. {
  91. NSAssert(regex&&regex.length>0, @"regularExpressionForKey:参数不得为空");
  92. if (self.emojiRegularExpressions[regex]) {
  93. return self.emojiRegularExpressions[regex];
  94. }
  95. NSRegularExpression *re = [[NSRegularExpression alloc] initWithPattern:regex options:NSRegularExpressionCaseInsensitive error:nil];
  96. NSAssert(re,@"正则%@有误",regex);
  97. self.emojiRegularExpressions[regex] = re;
  98. return self.emojiRegularExpressions[regex];
  99. }
  100. @end
  101. @interface TTTAttributedLabel(MLEmojiLabel)
  102. @property (readwrite, nonatomic, strong) TTTAttributedLabelLink *activeLink;
  103. - (void)commonInit;
  104. - (NSArray *)addLinksWithTextCheckingResults:(NSArray *)results
  105. attributes:(NSDictionary *)attributes;
  106. - (void)drawStrike:(CTFrameRef)frame
  107. inRect:(CGRect)rect
  108. context:(CGContextRef)c;
  109. @end
  110. @interface MLEmojiLabel()
  111. @property (nonatomic, weak) NSRegularExpression *customEmojiRegularExpression;
  112. @property (nonatomic, weak) NSDictionary *customEmojiDictionary; //这玩意如果有也是在MLEmojiLabelPlistManager单例里面存着
  113. @property (nonatomic, assign) BOOL ignoreSetText;
  114. //留个初始副本
  115. @property (nonatomic, copy) id emojiText;
  116. @end
  117. @implementation MLEmojiLabel
  118. #pragma mark - 表情包字典
  119. + (NSDictionary *)emojiDictionary {
  120. static NSDictionary *emojiDictionary = nil;
  121. static dispatch_once_t onceToken;
  122. dispatch_once(&onceToken, ^{
  123. NSString *emojiFilePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"expressionImage.plist"];
  124. emojiDictionary = [[NSDictionary alloc] initWithContentsOfFile:emojiFilePath];
  125. });
  126. return emojiDictionary;
  127. }
  128. #pragma mark - 表情 callback
  129. typedef struct CustomGlyphMetrics {
  130. CGFloat ascent;
  131. CGFloat descent;
  132. CGFloat width;
  133. } CustomGlyphMetrics, *CustomGlyphMetricsRef;
  134. static void deallocCallback(void *refCon) {
  135. (void)(free(refCon)), refCon = NULL;
  136. }
  137. static CGFloat ascentCallback(void *refCon) {
  138. CustomGlyphMetricsRef metrics = (CustomGlyphMetricsRef)refCon;
  139. return metrics->ascent;
  140. }
  141. static CGFloat descentCallback(void *refCon) {
  142. CustomGlyphMetricsRef metrics = (CustomGlyphMetricsRef)refCon;
  143. return metrics->descent;
  144. }
  145. static CGFloat widthCallback(void *refCon) {
  146. CustomGlyphMetricsRef metrics = (CustomGlyphMetricsRef)refCon;
  147. return metrics->width;
  148. }
  149. #pragma mark - 初始化和TTT的一些修正
  150. - (void)commonInit {
  151. [super commonInit];
  152. self.numberOfLines = 0;
  153. self.font = [UIFont systemFontOfSize:14.0];
  154. self.textColor = [UIColor blackColor];
  155. self.backgroundColor = [UIColor clearColor];
  156. self.lineBreakMode = NSLineBreakByCharWrapping;
  157. self.lineSpacing = kLineSpacing; //默认行间距
  158. //链接默认样式重新设置
  159. NSMutableDictionary *mutableLinkAttributes = [@{(NSString *)kCTUnderlineStyleAttributeName:@(NO)}mutableCopy];
  160. NSMutableDictionary *mutableActiveLinkAttributes = [@{(NSString *)kCTUnderlineStyleAttributeName:@(NO)}mutableCopy];
  161. UIColor *commonLinkColor = [UIColor colorWithRed:0.112 green:0.000 blue:0.791 alpha:1.000];
  162. //点击时候的背景色
  163. [mutableActiveLinkAttributes setValue:(__bridge id)[[UIColor colorWithWhite:0.631 alpha:1.000] CGColor] forKey:(NSString *)kTTTBackgroundFillColorAttributeName];
  164. if ([NSMutableParagraphStyle class]) {
  165. [mutableLinkAttributes setObject:commonLinkColor forKey:(NSString *)kCTForegroundColorAttributeName];
  166. [mutableActiveLinkAttributes setObject:commonLinkColor forKey:(NSString *)kCTForegroundColorAttributeName];
  167. } else {
  168. [mutableLinkAttributes setObject:(__bridge id)[commonLinkColor CGColor] forKey:(NSString *)kCTForegroundColorAttributeName];
  169. [mutableActiveLinkAttributes setObject:(__bridge id)[commonLinkColor CGColor] forKey:(NSString *)kCTForegroundColorAttributeName];
  170. }
  171. self.linkAttributes = [NSDictionary dictionaryWithDictionary:mutableLinkAttributes];
  172. self.activeLinkAttributes = [NSDictionary dictionaryWithDictionary:mutableActiveLinkAttributes];
  173. }
  174. /**
  175. * 如果是有attributedText的情况下,有可能会返回少那么点的,这里矫正下
  176. *
  177. */
  178. - (CGSize)sizeThatFits:(CGSize)size {
  179. if (!self.attributedText) {
  180. return [super sizeThatFits:size];
  181. }
  182. CGSize rSize = [super sizeThatFits:size];
  183. rSize.height +=1;
  184. return rSize;
  185. }
  186. //这里是抄TTT里的,因为他不是放在外面的
  187. static inline CGFloat TTTFlushFactorForTextAlignment(NSTextAlignment textAlignment) {
  188. switch (textAlignment) {
  189. case NSTextAlignmentCenter:
  190. return 0.5f;
  191. case NSTextAlignmentRight:
  192. return 1.0f;
  193. case NSTextAlignmentLeft:
  194. default:
  195. return 0.0f;
  196. }
  197. }
  198. #pragma mark - 绘制表情
  199. - (void)drawStrike:(CTFrameRef)frame
  200. inRect:(CGRect)rect
  201. context:(CGContextRef)c
  202. {
  203. [super drawStrike:frame inRect:rect context:c];
  204. CGFloat emojiWith = self.font.lineHeight*kEmojiWidthRatioWithLineHeight;
  205. CGFloat emojiOriginYOffset = self.font.lineHeight*kEmojiOriginYOffsetRatioWithLineHeight;
  206. //修正绘制offset,根据当前设置的textAlignment
  207. CGFloat flushFactor = TTTFlushFactorForTextAlignment(self.textAlignment);
  208. CFArrayRef lines = CTFrameGetLines(frame);
  209. NSInteger numberOfLines = self.numberOfLines > 0 ? MIN(self.numberOfLines, CFArrayGetCount(lines)) : CFArrayGetCount(lines);
  210. CGPoint lineOrigins[numberOfLines];
  211. CTFrameGetLineOrigins(frame, CFRangeMake(0, numberOfLines), lineOrigins);
  212. BOOL truncateLastLine = (self.lineBreakMode == NSLineBreakByTruncatingHead || self.lineBreakMode == NSLineBreakByTruncatingMiddle || self.lineBreakMode == NSLineBreakByTruncatingTail);
  213. CFRange textRange = CFRangeMake(0, (CFIndex)[self.attributedText length]);
  214. for (CFIndex lineIndex = 0; lineIndex < numberOfLines; lineIndex++) {
  215. CTLineRef line = CFArrayGetValueAtIndex(lines, lineIndex);
  216. //这里其实是能获取到当前行的真实origin.x,根据textAlignment,而lineBounds.origin.x其实是默认一直为0的(不会受textAlignment影响)
  217. CGFloat penOffset = (CGFloat)CTLineGetPenOffsetForFlush(line, flushFactor, rect.size.width);
  218. CFIndex truncationAttributePosition = -1;
  219. //检测如果是最后一行,是否有替换...
  220. if (lineIndex == numberOfLines - 1 && truncateLastLine) {
  221. // Check if the range of text in the last line reaches the end of the full attributed string
  222. CFRange lastLineRange = CTLineGetStringRange(line);
  223. if (!(lastLineRange.length == 0 && lastLineRange.location == 0) && lastLineRange.location + lastLineRange.length < textRange.location + textRange.length) {
  224. // Get correct truncationType and attribute position
  225. truncationAttributePosition = lastLineRange.location;
  226. NSLineBreakMode lineBreakMode = self.lineBreakMode;
  227. // Multiple lines, only use UILineBreakModeTailTruncation
  228. if (numberOfLines != 1) {
  229. lineBreakMode = NSLineBreakByTruncatingTail;
  230. }
  231. switch (lineBreakMode) {
  232. case NSLineBreakByTruncatingHead:
  233. break;
  234. case NSLineBreakByTruncatingMiddle:
  235. truncationAttributePosition += (lastLineRange.length / 2);
  236. break;
  237. case NSLineBreakByTruncatingTail:
  238. default:
  239. truncationAttributePosition += (lastLineRange.length - 1);
  240. break;
  241. }
  242. //如果要在truncationAttributePosition这个位置画表情需要忽略
  243. }
  244. }
  245. //找到当前行的每一个要素,姑且这么叫吧。可以理解为有单独的attr属性的各个range。
  246. for (id glyphRun in (__bridge NSArray *)CTLineGetGlyphRuns(line)) {
  247. //找到此要素所对应的属性
  248. NSDictionary *attributes = (__bridge NSDictionary *)CTRunGetAttributes((__bridge CTRunRef) glyphRun);
  249. //判断是否有图像,如果有就绘制上去
  250. NSString *imageName = attributes[kCustomGlyphAttributeImageName];
  251. if (imageName) {
  252. CFRange glyphRange = CTRunGetStringRange((__bridge CTRunRef)glyphRun);
  253. if (glyphRange.location == truncationAttributePosition) {
  254. //这里因为glyphRange的length肯定为1,所以只做这一个判断足够
  255. continue;
  256. }
  257. CGRect runBounds = CGRectZero;
  258. CGFloat runAscent = 0.0f;
  259. CGFloat runDescent = 0.0f;
  260. runBounds.size.width = (CGFloat)CTRunGetTypographicBounds((__bridge CTRunRef)glyphRun, CFRangeMake(0, 0), &runAscent, &runDescent, NULL);
  261. if (runBounds.size.width!=emojiWith) {
  262. //这一句是为了在某些情况下,例如单行省略号模式下,默认行为会将个别表情的runDelegate改变,也就改变了其大小。这时候会引起界面上错乱,这里做下检测(浮点数做等于判断似乎有点操蛋啊。。)
  263. continue;
  264. }
  265. runBounds.size.height = runAscent + runDescent;
  266. CGFloat xOffset = 0.0f;
  267. switch (CTRunGetStatus((__bridge CTRunRef)glyphRun)) {
  268. case kCTRunStatusRightToLeft:
  269. xOffset = CTLineGetOffsetForStringIndex(line, glyphRange.location + glyphRange.length, NULL);
  270. break;
  271. default:
  272. xOffset = CTLineGetOffsetForStringIndex(line, glyphRange.location, NULL);
  273. break;
  274. }
  275. runBounds.origin.x = penOffset + xOffset;
  276. runBounds.origin.y = lineOrigins[lineIndex].y;
  277. runBounds.origin.y -= runDescent;
  278. NSString *imagePath = [self.customEmojiBundleName?:@"QMEmoticon.bundle" stringByAppendingPathComponent:imageName];
  279. UIImage *image = [UIImage imageNamed:imagePath];
  280. runBounds.origin.y -= emojiOriginYOffset; //稍微矫正下。
  281. CGContextDrawImage(c, runBounds, image.CGImage);
  282. }
  283. }
  284. }
  285. }
  286. #pragma mark - main
  287. /**
  288. * 返回经过表情识别处理的Attributed字符串
  289. */
  290. - (NSMutableAttributedString*)mutableAttributeStringWithEmojiText:(NSAttributedString *)emojiText
  291. {
  292. //获取所有表情的位置
  293. // NSArray *emojis = [kEmojiRegularExpression() matchesInString:emojiText
  294. // options:NSMatchingWithTransparentBounds
  295. // range:NSMakeRange(0, [emojiText length])];
  296. NSArray *emojis = nil;
  297. if (self.customEmojiRegularExpression) {
  298. //自定义表情正则
  299. emojis = [self.customEmojiRegularExpression matchesInString:emojiText.string
  300. options:NSMatchingWithTransparentBounds
  301. range:NSMakeRange(0, [emojiText length])];
  302. }else{
  303. emojis = [kSlashEmojiRegularExpression() matchesInString:emojiText.string
  304. options:NSMatchingWithTransparentBounds
  305. range:NSMakeRange(0, [emojiText length])];
  306. }
  307. NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] init];
  308. NSUInteger location = 0;
  309. CGFloat emojiWith = self.font.lineHeight*kEmojiWidthRatioWithLineHeight;
  310. for (NSTextCheckingResult *result in emojis) {
  311. NSRange range = result.range;
  312. NSAttributedString *attSubStr = [emojiText attributedSubstringFromRange:NSMakeRange(location, range.location - location)];
  313. [attrStr appendAttributedString:attSubStr];
  314. location = range.location + range.length;
  315. NSAttributedString *emojiKey = [emojiText attributedSubstringFromRange:range];
  316. NSDictionary *emojiDict = self.customEmojiRegularExpression?self.customEmojiDictionary:[MLEmojiLabel emojiDictionary];
  317. //如果当前获得key后面有多余的,这个需要记录下
  318. NSAttributedString *otherAppendStr = nil;
  319. NSString *imageName = emojiDict[emojiKey.string];
  320. if (!self.customEmojiRegularExpression) {
  321. //微信的表情没有结束符号,所以有可能会发现过长的只有头部才是表情的段,需要循环检测一次。微信最大表情特殊字符是8个长度,检测8次即可
  322. if (!imageName&&emojiKey.length>2) {
  323. NSUInteger maxDetctIndex = emojiKey.length>8+2?8:emojiKey.length-2;
  324. //从头开始检测是否有对应的
  325. for (NSUInteger i=0; i<maxDetctIndex; i++) {
  326. // NSLog(@"%@",[emojiKey.string substringToIndex:3+i]);
  327. imageName = emojiDict[[emojiKey.string substringToIndex:3+i]];
  328. if (imageName) {
  329. otherAppendStr = [emojiKey attributedSubstringFromRange:NSMakeRange(3+i, emojiKey.length-3-i)];
  330. break;
  331. }
  332. }
  333. }
  334. }
  335. if (imageName) {
  336. // 这里不用空格,空格有个问题就是连续空格的时候只显示在一行
  337. NSMutableAttributedString *replaceStr = [[NSMutableAttributedString alloc] initWithString:kEmojiReplaceCharacter];
  338. NSRange __range = NSMakeRange([attrStr length], 1);
  339. [attrStr appendAttributedString:replaceStr];
  340. if (otherAppendStr) { //有其他需要添加的
  341. [attrStr appendAttributedString:otherAppendStr];
  342. }
  343. // 定义回调函数
  344. CTRunDelegateCallbacks callbacks;
  345. callbacks.version = kCTRunDelegateCurrentVersion;
  346. callbacks.getAscent = ascentCallback;
  347. callbacks.getDescent = descentCallback;
  348. callbacks.getWidth = widthCallback;
  349. callbacks.dealloc = deallocCallback;
  350. // 这里设置下需要绘制的图片的大小,这里我自定义了一个结构体以便于存储数据
  351. CustomGlyphMetricsRef metrics = malloc(sizeof(CustomGlyphMetrics));
  352. metrics->width = emojiWith;
  353. metrics->ascent = 1/(1+kAscentDescentScale)*metrics->width;
  354. metrics->descent = metrics->ascent*kAscentDescentScale;
  355. CTRunDelegateRef delegate = CTRunDelegateCreate(&callbacks, metrics);
  356. [attrStr addAttribute:(NSString *)kCTRunDelegateAttributeName
  357. value:(__bridge id)delegate
  358. range:__range];
  359. CFRelease(delegate);
  360. // 设置自定义属性,绘制的时候需要用到
  361. [attrStr addAttribute:kCustomGlyphAttributeImageName
  362. value:imageName
  363. range:__range];
  364. } else {
  365. [attrStr appendAttributedString:emojiKey];
  366. }
  367. }
  368. if (location < [emojiText length]) {
  369. NSRange range = NSMakeRange(location, [emojiText length] - location);
  370. NSAttributedString *attrSubStr = [emojiText attributedSubstringFromRange:range];
  371. [attrStr appendAttributedString:attrSubStr];
  372. }
  373. return attrStr;
  374. }
  375. - (void)setText:(id)text
  376. {
  377. NSParameterAssert(!text || [text isKindOfClass:[NSAttributedString class]] || [text isKindOfClass:[NSString class]]);
  378. if (self.ignoreSetText) {
  379. [super setText:text];
  380. return;
  381. }
  382. if (!text) {
  383. self.emojiText = nil;
  384. [super setText:nil];
  385. return;
  386. }
  387. //记录下原始的留作备份使用
  388. self.emojiText = text;
  389. NSMutableAttributedString *mutableAttributedString = nil;
  390. if (self.disableEmoji) {
  391. mutableAttributedString = [text isKindOfClass:[NSAttributedString class]]?[text mutableCopy]:[[NSMutableAttributedString alloc]initWithString:text];
  392. //直接设置text即可,这里text可能为attrString,也可能为String,使用TTT的默认行为
  393. [super setText:text];
  394. }else{
  395. //如果是String,必须通过setText:afterInheritingLabelAttributesAndConfiguringWithBlock:来添加一些默认属性,例如字体颜色。这是TTT的做法,不可避免
  396. if([text isKindOfClass:[NSString class]]){
  397. mutableAttributedString = [self mutableAttributeStringWithEmojiText:[[NSAttributedString alloc] initWithString:text]];
  398. //这里面会调用 self setText:,所以需要做个标记避免下无限循环
  399. self.ignoreSetText = YES;
  400. [super setText:mutableAttributedString afterInheritingLabelAttributesAndConfiguringWithBlock:nil];
  401. self.ignoreSetText = NO;
  402. }else{
  403. mutableAttributedString = [self mutableAttributeStringWithEmojiText:text];
  404. //这里虽然会调用
  405. [super setText:mutableAttributedString];
  406. }
  407. }
  408. NSRange stringRange = NSMakeRange(0, mutableAttributedString.length);
  409. // NSRegularExpression * const regexps[] = {kURLRegularExpression(),kEmailRegularExpression(),kPhoneNumerRegularExpression(),kAtRegularExpression(),kPoundSignRegularExpression()};
  410. NSRegularExpression * const regexps[] = {kRobotRegularExpression(), kURLRegularExpression()};
  411. NSMutableArray *results = [NSMutableArray array];
  412. // NSUInteger maxIndex = self.isNeedAtAndPoundSign?kURLActionCount:kURLActionCount-2;
  413. for (NSUInteger i=0; i<2; i++) {
  414. if (self.disableThreeCommon&&i<kURLActionCount-2) {
  415. continue;
  416. }
  417. NSString *urlAction = kURLActions[i];
  418. [regexps[i] enumerateMatchesInString:mutableAttributedString.string options:0 range:stringRange usingBlock:^(NSTextCheckingResult *result, __unused NSMatchingFlags flags, __unused BOOL *stop) {
  419. //检查是否和之前记录的有交集,有的话则忽略
  420. for (NSTextCheckingResult *record in results){
  421. if (NSMaxRange(NSIntersectionRange(record.range, result.range))>0){
  422. return;
  423. }
  424. }
  425. //添加链接
  426. NSString *actionString = [NSString stringWithFormat:@"%@%@",urlAction,[self.text substringWithRange:result.range]];
  427. //这里暂时用NSTextCheckingTypeCorrection类型的传递消息吧
  428. //因为有自定义的类型出现,所以这样方便点。
  429. NSTextCheckingResult *aResult = [NSTextCheckingResult correctionCheckingResultWithRange:result.range replacementString:actionString];
  430. [results addObject:aResult];
  431. }];
  432. }
  433. //这里直接调用父类私有方法,好处能内部只会setNeedDisplay一次。一次更新所有添加的链接
  434. [super addLinksWithTextCheckingResults:results attributes:self.linkAttributes];
  435. if (self.checkResults) {
  436. NSMutableDictionary *mutableLinkAttributes = [@{(NSString *)kCTUnderlineStyleAttributeName:@(NO)}mutableCopy];
  437. UIColor *commonLinkColor = self.checkColor;
  438. [mutableLinkAttributes setObject:commonLinkColor forKey:(NSString *)kCTForegroundColorAttributeName];
  439. [super addLinksWithTextCheckingResults:self.checkResults attributes:mutableLinkAttributes];
  440. }
  441. }
  442. #pragma mark - size fit result
  443. - (CGSize)preferredSizeWithMaxWidth:(CGFloat)maxWidth
  444. {
  445. maxWidth = maxWidth - self.textInsets.left - self.textInsets.right;
  446. return [self sizeThatFits:CGSizeMake(maxWidth, CGFLOAT_MAX)];
  447. }
  448. #pragma mark - setter
  449. - (void)setIsNeedAtAndPoundSign:(BOOL)isNeedAtAndPoundSign
  450. {
  451. _isNeedAtAndPoundSign = isNeedAtAndPoundSign;
  452. self.text = self.emojiText; //简单重新绘制处理下
  453. }
  454. - (void)setLineBreakMode:(NSLineBreakMode)lineBreakMode
  455. {
  456. [super setLineBreakMode:lineBreakMode];
  457. self.text = self.emojiText; //简单重新绘制处理下
  458. }
  459. - (void)setDisableEmoji:(BOOL)disableEmoji
  460. {
  461. _disableEmoji = disableEmoji;
  462. self.text = self.emojiText; //简单重新绘制处理下
  463. }
  464. - (void)setDisableThreeCommon:(BOOL)disableThreeCommon
  465. {
  466. _disableThreeCommon = disableThreeCommon;
  467. self.text = self.emojiText; //简单重新绘制处理下
  468. }
  469. - (void)setCustomEmojiRegex:(NSString *)customEmojiRegex
  470. {
  471. _customEmojiRegex = [customEmojiRegex copy];
  472. if (customEmojiRegex&&customEmojiRegex.length>0) {
  473. self.customEmojiRegularExpression = [[MLEmojiLabelRegexPlistManager sharedInstance]regularExpressionForRegex:customEmojiRegex];
  474. }else{
  475. self.customEmojiRegularExpression = nil;
  476. }
  477. self.text = self.emojiText; //简单重新绘制处理下
  478. }
  479. - (void)setCustomEmojiPlistName:(NSString *)customEmojiPlistName
  480. {
  481. if (customEmojiPlistName&&customEmojiPlistName.length>0&&![[customEmojiPlistName lowercaseString] hasSuffix:@".plist"]) {
  482. customEmojiPlistName = [customEmojiPlistName stringByAppendingString:@".plist"];
  483. }
  484. _customEmojiPlistName = [customEmojiPlistName copy];
  485. if (customEmojiPlistName&&customEmojiPlistName.length>0) {
  486. self.customEmojiDictionary = [[MLEmojiLabelRegexPlistManager sharedInstance]emojiDictForKey:customEmojiPlistName];
  487. }else{
  488. self.customEmojiDictionary = nil;
  489. }
  490. self.text = self.emojiText; //简单重新绘制处理下
  491. }
  492. - (void)setCustomEmojiBundleName:(NSString *)customEmojiBundleName
  493. {
  494. if (customEmojiBundleName&&customEmojiBundleName.length>0&&![[customEmojiBundleName lowercaseString] hasSuffix:@".bundle"]) {
  495. customEmojiBundleName = [customEmojiBundleName stringByAppendingString:@".bundle"];
  496. }
  497. _customEmojiBundleName = [customEmojiBundleName copy];
  498. self.text = self.emojiText; //简单重新绘制处理下
  499. }
  500. - (void)setFont:(UIFont *)font
  501. {
  502. [super setFont:font];
  503. self.text = self.emojiText; //简单重新绘制处理下
  504. }
  505. #pragma mark - select link override
  506. - (void)touchesEnded:(NSSet *)touches
  507. withEvent:(UIEvent *)event
  508. {
  509. //如果delegate实现了mlEmojiLabel自身的选择link方法
  510. if(self.delegate&&[self.delegate respondsToSelector:@selector(mlEmojiLabel:didSelectLink:withType:)]){
  511. if (self.activeLink&&self.activeLink.result.resultType==NSTextCheckingTypeCorrection) {
  512. NSTextCheckingResult *result = self.activeLink.result;
  513. //判断消息类型
  514. for (NSUInteger i=0; i<kURLActionCount; i++) {
  515. if ([result.replacementString hasPrefix:kURLActions[i]]) {
  516. NSString *content = [result.replacementString substringFromIndex:kURLActions[i].length];
  517. //type的数组和i刚好对应
  518. [self.delegate mlEmojiLabel:self didSelectLink:content withType:i];
  519. self.activeLink = nil;
  520. return;
  521. }
  522. }
  523. }
  524. }
  525. [super touchesEnded:touches withEvent:event];
  526. }
  527. #pragma mark - UIResponderStandardEditActions
  528. - (void)copy:(__unused id)sender {
  529. if (!self.emojiText) {
  530. return;
  531. }
  532. NSString *text = [self.emojiText isKindOfClass:[NSAttributedString class]]?((NSAttributedString*)self.emojiText).string:self.emojiText;
  533. if (text.length>0) {
  534. [[UIPasteboard generalPasteboard] setString:text];
  535. }
  536. }
  537. //#pragma mark - other
  538. //为了生成plist方便的一个方法罢了
  539. //- (void)initPlist
  540. //{
  541. // NSString *testString = @"/::)/::~/::B/::|/:8-)/::</::$/::X/::Z/::'(/::-|/::@/::P/::D/::O/::(/::+/:--b/::Q/::T/:,@P/:,@-D/::d/:,@o/::g/:|-)/::!/::L/::>/::,@/:,@f/::-S/:?/:,@x/:,@@/::8/:,@!/:!!!/:xx/:bye/:wipe/:dig/:handclap/:&-(/:B-)/:<@/:@>/::-O/:>-|/:P-(/::'|/:X-)/::*/:@x/:8*/:pd/:<W>/:beer/:basketb/:oo/:coffee/:eat/:pig/:rose/:fade/:showlove/:heart/:break/:cake/:li/:bome/:kn/:footb/:ladybug/:shit/:moon/:sun/:gift/:hug/:strong/:weak/:share/:v/:@)/:jj/:@@/:bad/:lvu/:no/:ok/:love/:<L>/:jump/:shake/:<O>/:circle/:kotow/:turn/:skip/:oY";
  542. // NSMutableArray *testArray = [NSMutableArray array];
  543. // NSMutableDictionary *testDict = [NSMutableDictionary dictionary];
  544. // [kSlashEmojiRegularExpression() enumerateMatchesInString:testString options:0 range:NSMakeRange(0, testString.length) usingBlock:^(NSTextCheckingResult *result, __unused NSMatchingFlags flags, __unused BOOL *stop) {
  545. // [testArray addObject:[testString substringWithRange:result.range]];
  546. // [testDict setObject:[NSString stringWithFormat:@"Expression_%u",testArray.count] forKey:[testString substringWithRange:result.range]];
  547. // }];
  548. //
  549. // NSString *documentDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
  550. // NSString *doc = [NSString stringWithFormat:@"%@/expression.plist",documentDir];
  551. // NSLog(@"%@,length:%u",doc,testArray.count);
  552. // if ([testArray writeToFile:doc atomically:YES]) {
  553. // NSLog(@"归档expression.plist成功");
  554. // }
  555. // doc = [NSString stringWithFormat:@"%@/expressionImage.plist",documentDir];
  556. // if ([testDict writeToFile:doc atomically:YES]) {
  557. // NSLog(@"归档到expressionImage.plist成功");
  558. // }
  559. //
  560. // // NSString *testString = @"[微笑][撇嘴][色][发呆][得意][流泪][害羞][闭嘴][睡][大哭][尴尬][发怒][调皮][呲牙][惊讶][难过][酷][冷汗][抓狂][吐][偷笑][愉快][白眼][傲慢][饥饿][困][惊恐][流汗][憨笑][悠闲][奋斗][咒骂][疑问][嘘][晕][疯了][衰][骷髅][敲打][再见][擦汗][抠鼻][鼓掌][糗大了][坏笑][左哼哼][右哼哼][哈欠][鄙视][委屈][快哭了][阴险][亲亲][吓][可怜][菜刀][西瓜][啤酒][篮球][乒乓][咖啡][饭][猪头][玫瑰][凋谢][嘴唇][爱心][心碎][蛋糕][闪电][炸弹][刀][足球][瓢虫][便便][月亮][太阳][礼物][拥抱][强][弱][握手][胜利][抱拳][勾引][拳头][差劲][爱你][NO][OK][爱情][飞吻][跳跳][发抖][怄火][转圈][磕头][回头][跳绳][投降]";
  561. // // NSMutableArray *testArray = [NSMutableArray array];
  562. // // NSMutableDictionary *testDict = [NSMutableDictionary dictionary];
  563. // // [kEmojiRegularExpression() enumerateMatchesInString:testString options:0 range:NSMakeRange(0, testString.length) usingBlock:^(NSTextCheckingResult *result, __unused NSMatchingFlags flags, __unused BOOL *stop) {
  564. // // [testArray addObject:[testString substringWithRange:result.range]];
  565. // // [testDict setObject:[NSString stringWithFormat:@"Expression_%ld",testArray.count] forKey:[testString substringWithRange:result.range]];
  566. // // }];
  567. // // NSString *documentDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
  568. // // NSString *doc = [NSString stringWithFormat:@"%@/expression.plist",documentDir];
  569. // // NSLog(@"%@,length:%ld",doc,testArray.count);
  570. // // if ([testArray writeToFile:doc atomically:YES]) {
  571. // // NSLog(@"归档expression.plist成功");
  572. // // }
  573. // // doc = [NSString stringWithFormat:@"%@/expressionImage.plist",documentDir];
  574. // // if ([testDict writeToFile:doc atomically:YES]) {
  575. // // NSLog(@"归档到expressionImage.plist成功");
  576. // // }
  577. //
  578. //
  579. //}
  580. @end