// // AttributedLabel.m // WildFireChat // // Created by heavyrain.lee on 2018/5/15. // Copyright © 2018 WildFireChat. All rights reserved. // #import "AttributedLabel.h" #import @interface AttributedLabel() @property(nonatomic, strong)NSMutableArray *stringArray; @property(nonatomic, strong)NSMutableArray *rangeArray; @end @implementation AttributedLabel - (void)setText:(NSString *)text { self.attributedText = [self subStr:text]; self.userInteractionEnabled = YES; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; CFIndex index = [self characterIndexAtPoint:[touch locationInView:self]]; for(NSValue *value in self.rangeArray) { NSRange range=[value rangeValue]; if (range.location <= index && (range.location+range.length) >= index) { NSInteger i=[self.rangeArray indexOfObject:value]; NSString *str = self.stringArray[i]; NSLog(@"touch url %@", str); NSString *pattern =@"[0-9]{5,12}"; NSPredicate*pred = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",pattern]; BOOL isNumber = [pred evaluateWithObject:str]; if (isNumber) { if ([self.attributedLabelDelegate respondsToSelector:@selector(didSelectPhoneNumber:)]) { [self.attributedLabelDelegate didSelectPhoneNumber:str]; } } else { if ([self.attributedLabelDelegate respondsToSelector:@selector(didSelectUrl:)]) { [self.attributedLabelDelegate didSelectUrl:str]; } } } } [super touchesBegan:touches withEvent:event]; } -(NSMutableAttributedString*)subStr:(NSString *)string { if (!string) { return nil; } NSError *error; //可以识别url的正则表达式 NSString *regulaStr = @"((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\\.\\-~!@#$%^&*+?:_/=<>]*)?)"; NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regulaStr options:NSRegularExpressionCaseInsensitive error:&error]; NSArray *arrayOfAllMatches = [regex matchesInString:string options:0 range:NSMakeRange(0, [string length])]; NSMutableArray *arr=[[NSMutableArray alloc]init]; NSMutableArray *rangeArr=[[NSMutableArray alloc]init]; self.stringArray = arr; self.rangeArray = rangeArr; for (NSTextCheckingResult *match in arrayOfAllMatches) { NSString* substringForMatch; substringForMatch = [string substringWithRange:match.range]; [arr addObject:substringForMatch]; } NSString *subStr=string; for (NSString *str in arr) { [rangeArr addObject:[self rangesOfString:str inString:subStr]]; } NSString *pattern =@"[0-9]{5,11}"; regex = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:&error]; arrayOfAllMatches = [regex matchesInString:string options:0 range:NSMakeRange(0, [string length])]; NSMutableArray *telArr = [[NSMutableArray alloc] init]; for (NSTextCheckingResult *match in arrayOfAllMatches) { NSString* substringForMatch; substringForMatch = [string substringWithRange:match.range]; [arr addObject:substringForMatch]; [telArr addObject:substringForMatch]; } subStr=string; for (NSString *str in telArr) { [rangeArr addObject:[self rangesOfString:str inString:subStr]]; } NSMutableAttributedString *attributedText; attributedText=[[NSMutableAttributedString alloc]initWithString:subStr attributes:@{NSFontAttributeName :self.font}]; for(NSValue *value in rangeArr) { NSInteger index=[rangeArr indexOfObject:value]; NSRange range=[value rangeValue]; [attributedText addAttribute:NSLinkAttributeName value:[NSURL URLWithString:[[arr objectAtIndex:index] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]] range:range]; [attributedText addAttribute:NSForegroundColorAttributeName value:[UIColor blueColor] range:range]; } return attributedText; } //获取查找字符串在母串中的NSRange - (NSValue *)rangesOfString:(NSString *)searchString inString:(NSString *)str { NSRange searchRange = NSMakeRange(0, [str length]); NSRange range; if ((range = [str rangeOfString:searchString options:0 range:searchRange]).location != NSNotFound) { searchRange = NSMakeRange(NSMaxRange(range), [str length] - NSMaxRange(range)); } return [NSValue valueWithRange:range]; } - (CFIndex)characterIndexAtPoint:(CGPoint)point { //////// NSMutableAttributedString* optimizedAttributedText = [self.attributedText mutableCopy]; [self.attributedText enumerateAttribute:(NSString*)kCTParagraphStyleAttributeName inRange:NSMakeRange(0, [optimizedAttributedText length]) options:0 usingBlock:^(id value, NSRange range, BOOL *stop) { if (value == nil) { return ; } NSMutableParagraphStyle* paragraphStyle = [value mutableCopy]; if ([paragraphStyle lineBreakMode] == kCTLineBreakByTruncatingTail) { [paragraphStyle setLineBreakMode:kCTLineBreakByWordWrapping]; } [optimizedAttributedText removeAttribute:(NSString*)kCTParagraphStyleAttributeName range:range]; [optimizedAttributedText addAttribute:(NSString*)kCTParagraphStyleAttributeName value:paragraphStyle range:range]; }]; //////// if (!CGRectContainsPoint(self.bounds, point)) { return NSNotFound; } CGRect textRect = [self textRect]; if (!CGRectContainsPoint(textRect, point)) { return NSNotFound; } // Offset tap coordinates by textRect origin to make them relative to the origin of frame point = CGPointMake(point.x - textRect.origin.x, point.y - textRect.origin.y); // Convert tap coordinates (start at top left) to CT coordinates (start at bottom left) point = CGPointMake(point.x, textRect.size.height - point.y); ////// CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)optimizedAttributedText); CGMutablePathRef path = CGPathCreateMutable(); CGPathAddRect(path, NULL, textRect); CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, [self.attributedText length]), path, NULL); if (frame == NULL) { CFRelease(path); return NSNotFound; } CFArrayRef lines = CTFrameGetLines(frame); NSInteger numberOfLines = self.numberOfLines > 0 ? MIN(self.numberOfLines, CFArrayGetCount(lines)) : CFArrayGetCount(lines); //NSLog(@"num lines: %d", numberOfLines); if (numberOfLines == 0) { CFRelease(frame); CFRelease(path); return NSNotFound; } NSUInteger idx = NSNotFound; CGPoint lineOrigins[numberOfLines]; CTFrameGetLineOrigins(frame, CFRangeMake(0, numberOfLines), lineOrigins); for (CFIndex lineIndex = 0; lineIndex < numberOfLines; lineIndex++) { CGPoint lineOrigin = lineOrigins[lineIndex]; CTLineRef line = CFArrayGetValueAtIndex(lines, lineIndex); // Get bounding information of line CGFloat ascent, descent, leading, width; width = CTLineGetTypographicBounds(line, &ascent, &descent, &leading); CGFloat yMin = floor(lineOrigin.y - descent); CGFloat yMax = ceil(lineOrigin.y + ascent); // Check if we've already passed the line if (point.y > yMax) { break; } // Check if the point is within this line vertically if (point.y >= yMin) { // Check if the point is within this line horizontally if (point.x >= lineOrigin.x && point.x <= lineOrigin.x + width) { // Convert CT coordinates to line-relative coordinates CGPoint relativePoint = CGPointMake(point.x - lineOrigin.x, point.y - lineOrigin.y); idx = CTLineGetStringIndexForPosition(line, relativePoint); break; } } } CFRelease(frame); CFRelease(path); return idx; } - (CGRect)textRect { CGRect textRect = [self textRectForBounds:self.bounds limitedToNumberOfLines:self.numberOfLines]; textRect.origin.y = (self.bounds.size.height - textRect.size.height)/2; if (self.textAlignment == NSTextAlignmentCenter) { textRect.origin.x = (self.bounds.size.width - textRect.size.width)/2; } if (self.textAlignment == NSTextAlignmentRight) { textRect.origin.x = self.bounds.size.width - textRect.size.width; } return textRect; } @end