《省钱达人》与《猎豆优选》UI相同版。域名tbk

MLEmojiLabel.m 31KB

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