```
//
// UILabel+YBAttributeTextTapAction.m
//
// Created by LYB on 16/7/1.
// Copyright © 2016年 LYB. All rights reserved.
//
#import "UILabel+YBAttributeTextTapAction.h"
#import
#import
#import
@interfaceYBAttributeModel :NSObject
@property (nonatomic, copy) NSString *str;
@property (nonatomic) NSRange range;
@end
@implementationYBAttributeModel
@end
@implementationUILabel (YBAttributeTextTapAction)
#pragma mark - AssociatedObjects
- (NSMutableArray*)attributeStrings
{
return objc_getAssociatedObject(self, _cmd);
}
- (void)setAttributeStrings:(NSMutableArray*)attributeStrings
{
objc_setAssociatedObject(self, @selector(attributeStrings), attributeStrings, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSMutableDictionary *)effectDic
{
return objc_getAssociatedObject(self, _cmd);
}
- (void)setEffectDic:(NSMutableDictionary*)effectDic
{
objc_setAssociatedObject(self, @selector(effectDic), effectDic, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)isTapAction
{
return [objc_getAssociatedObject(self, _cmd) boolValue];
}
- (void)setIsTapAction:(BOOL)isTapAction
{
objc_setAssociatedObject(self,@selector(isTapAction),@(isTapAction), OBJC_ASSOCIATION_ASSIGN);
}
- (void(^)(UILabel *, NSString *, NSRange, NSInteger))tapBlock
{
returnobjc_getAssociatedObject(self,_cmd);
}
- (void)setTapBlock:(void(^)(UILabel *, NSString *, NSRange, NSInteger))tapBlock
{
objc_setAssociatedObject(self,@selector(tapBlock), tapBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (id)delegate
{
returnobjc_getAssociatedObject(self,_cmd);
}
- (void)setDelegate:(id)delegate
{
objc_setAssociatedObject(self,@selector(delegate), delegate, OBJC_ASSOCIATION_ASSIGN);
}
- (BOOL)enabledTapEffect
{
return[objc_getAssociatedObject(self,_cmd) boolValue];
}
- (void)setEnabledTapEffect:(BOOL)enabledTapEffect
{
objc_setAssociatedObject(self,@selector(enabledTapEffect),@(enabledTapEffect), OBJC_ASSOCIATION_ASSIGN);
self.isTapEffect = enabledTapEffect;
}
- (BOOL)enlargeTapArea
{
NSNumber * number = objc_getAssociatedObject(self,_cmd);
if(!number) {
number =@(YES);
objc_setAssociatedObject(self,_cmd, number, OBJC_ASSOCIATION_ASSIGN);
}
return[number boolValue];
}
- (void)setEnlargeTapArea:(BOOL)enlargeTapArea
{
objc_setAssociatedObject(self,@selector(enlargeTapArea),@(enlargeTapArea), OBJC_ASSOCIATION_ASSIGN);
}
- (UIColor *)tapHighlightedColor
{
UIColor * color = objc_getAssociatedObject(self,_cmd);
if(!color) {
color = [UIColor lightGrayColor];
objc_setAssociatedObject(self,_cmd, color, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
returncolor;
}
- (void)setTapHighlightedColor:(UIColor *)tapHighlightedColor
{
objc_setAssociatedObject(self,@selector(tapHighlightedColor), tapHighlightedColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)isTapEffect
{
return[objc_getAssociatedObject(self,_cmd) boolValue];
}
- (void)setIsTapEffect:(BOOL)isTapEffect
{
objc_setAssociatedObject(self,@selector(isTapEffect),@(isTapEffect), OBJC_ASSOCIATION_ASSIGN);
}
#pragma mark - mainFunction
- (void)yb_addAttributeTapActionWithStrings:(NSArray *)strings tapClicked:(void(^) (UILabel * label, NSString *string, NSRange range, NSInteger index))tapClick
{
[selfyb_removeAttributeTapActions];
[selfyb_getRangesWithStrings:strings];
self.userInteractionEnabled =YES;
if(self.tapBlock != tapClick) {
self.tapBlock = tapClick;
}
}
- (void)yb_addAttributeTapActionWithStrings:(NSArray *)strings
delegate:(id )delegate
{
[selfyb_removeAttributeTapActions];
[selfyb_getRangesWithStrings:strings];
self.userInteractionEnabled =YES;
if(self.delegate != delegate) {
self.delegate = delegate;
}
}
- (void)yb_addAttributeTapActionWithRanges:(NSArray *)ranges tapClicked:(void(^)(UILabel *, NSString *, NSRange, NSInteger))tapClick
{
[selfyb_removeAttributeTapActions];
[selfyb_getRangesWithRanges:ranges];
self.userInteractionEnabled =YES;
if(self.tapBlock != tapClick) {
self.tapBlock = tapClick;
}
}
- (void)yb_addAttributeTapActionWithRanges:(NSArray *)ranges delegate:(id)delegate
{
[selfyb_removeAttributeTapActions];
[selfyb_getRangesWithRanges:ranges];
self.userInteractionEnabled =YES;
if(self.delegate != delegate) {
self.delegate = delegate;
}
}
- (void)yb_removeAttributeTapActions
{
self.tapBlock =nil;
self.delegate =nil;
self.effectDic =nil;
self.isTapAction =NO;
self.attributeStrings = [NSMutableArray array];
}
#pragma mark - touchAction
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if(!self.isTapAction) {
[supertouchesBegan:touches withEvent:event];
return;
}
if(objc_getAssociatedObject(self,@selector(enabledTapEffect))) {
self.isTapEffect =self.enabledTapEffect;
}
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
__weak typeof(self) weakSelf = self;
BOOLret = [selfyb_getTapFrameWithTouchPoint:point result:^(NSString *string, NSRange range, NSInteger index) {
if(weakSelf.isTapEffect) {
[weakSelf yb_saveEffectDicWithRange:range];
[weakSelf yb_tapEffectWithStatus:YES];
}
}];
if(!ret) {
[supertouchesBegan:touches withEvent:event];
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if(!self.isTapAction) {
[supertouchesEnded:touches withEvent:event];
return;
}
if(self.isTapEffect) {
[selfperformSelectorOnMainThread:@selector(yb_tapEffectWithStatus:) withObject:nilwaitUntilDone:NO];
}
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
__weak typeof(self) weakSelf = self;
BOOLret = [selfyb_getTapFrameWithTouchPoint:point result:^(NSString *string, NSRange range, NSInteger index) {
if(weakSelf.tapBlock) {
weakSelf.tapBlock (weakSelf, string, range, index);
}
if(weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(yb_tapAttributeInLabel:string:range:index:)]) {
[weakSelf.delegate yb_tapAttributeInLabel:weakSelf string:string range:range index:index];
}
}];
if(!ret) {
[supertouchesEnded:touches withEvent:event];
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
if(!self.isTapAction) {
[supertouchesCancelled:touches withEvent:event];
return;
}
if(self.isTapEffect) {
[selfperformSelectorOnMainThread:@selector(yb_tapEffectWithStatus:) withObject:nilwaitUntilDone:NO];
}
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
__weak typeof(self) weakSelf = self;
BOOLret = [selfyb_getTapFrameWithTouchPoint:point result:^(NSString *string, NSRange range, NSInteger index) {
if(weakSelf.tapBlock) {
weakSelf.tapBlock (weakSelf, string, range, index);
}
if(weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(yb_tapAttributeInLabel:string:range:index:)]) {
[weakSelf.delegate yb_tapAttributeInLabel:weakSelf string:string range:range index:index];
}
}];
if(!ret) {
[supertouchesCancelled:touches withEvent:event];
}
}
#pragma mark - getTapFrame
- (BOOL)yb_getTapFrameWithTouchPoint:(CGPoint)point result:(void(^) (NSString *string , NSRange range , NSInteger index))resultBlock
{
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)self.attributedText);
CGMutablePathRef Path = CGPathCreateMutable();
CGPathAddRect(Path,NULL, CGRectMake(0,0,self.bounds.size.width,self.bounds.size.height +20));
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0,0), Path,NULL);
CFArrayRef lines = CTFrameGetLines(frame);
CGFloat total_height = [selfyb_textSizeWithAttributedString:self.attributedText width:self.bounds.size.width numberOfLines:0].height;
if(!lines) {
CFRelease(frame);
CFRelease(framesetter);
CGPathRelease(Path);
returnNO;
}
CFIndex count = CFArrayGetCount(lines);
CGPoint origins[count];
CTFrameGetLineOrigins(frame, CFRangeMake(0,0), origins);
CGAffineTransform transform = [selfyb_transformForCoreText];
for(CFIndex i =0; i < count; i++) {
CGPoint linePoint = origins[i];
CTLineRef line = CFArrayGetValueAtIndex(lines, i);
CGRect flippedRect = [selfyb_getLineBounds:line point:linePoint];
CGRect rect = CGRectApplyAffineTransform(flippedRect, transform);
CGFloat lineOutSpace = (self.bounds.size.height - total_height) /2;
rect.origin.y = lineOutSpace + [selfyb_getLineOrign:line];
if(self.enlargeTapArea) {
rect.origin.y -=5;
rect.size.height +=10;
}
if(CGRectContainsPoint(rect, point)) {
CGPoint relativePoint = CGPointMake(point.x - CGRectGetMinX(rect), point.y - CGRectGetMinY(rect));
CFIndex index = CTLineGetStringIndexForPosition(line, relativePoint);
CGFloat offset;
CTLineGetOffsetForStringIndex(line, index, &offset);
if(offset > relativePoint.x) {
index = index -1;
}
NSInteger link_count =self.attributeStrings.count;
for(intj =0; j < link_count; j++) {
YBAttributeModel *model =self.attributeStrings[j];
NSRange link_range = model.range;
if(NSLocationInRange(index, link_range)) {
if(resultBlock) {
resultBlock (model.str , model.range , (NSInteger)j);
}
CFRelease(frame);
CFRelease(framesetter);
CGPathRelease(Path);
returnYES;
}
}
}
}
CFRelease(frame);
CFRelease(framesetter);
CGPathRelease(Path);
return NO;
}
- (CGAffineTransform)yb_transformForCoreText
{
returnCGAffineTransformScale(CGAffineTransformMakeTranslation(0,self.bounds.size.height),1.f, -1.f);
}
- (CGRect)yb_getLineBounds:(CTLineRef)line point:(CGPoint)point
{
CGFloat ascent =0.0f;
CGFloat descent =0.0f;
CGFloat leading =0.0f;
CGFloat width = (CGFloat)CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
CGFloat height =0.0f;
CFRange range = CTLineGetStringRange(line);
NSAttributedString * attributedString = [self.attributedText attributedSubstringFromRange:NSMakeRange(range.location, range.length)];
if([attributedString.string hasSuffix:@"\n"] && attributedString.string.length >1) {
attributedString = [attributedString attributedSubstringFromRange:NSMakeRange(0, attributedString.length -1)];
}
height = [selfyb_textSizeWithAttributedString:attributedString width:self.bounds.size.width numberOfLines:0].height;
returnCGRectMake(point.x, point.y , width, height);
}
- (CGFloat)yb_getLineOrign:(CTLineRef)line
{
CFRange range = CTLineGetStringRange(line);
if(range.location ==0) {
return0.;
}else{
NSAttributedString * attributedString = [self.attributedText attributedSubstringFromRange:NSMakeRange(0, range.location)];
if([attributedString.string hasSuffix:@"\n"] && attributedString.string.length >1) {
attributedString = [attributedString attributedSubstringFromRange:NSMakeRange(0, attributedString.length -1)];
}
return[selfyb_textSizeWithAttributedString:attributedString width:self.bounds.size.width numberOfLines:0].height;
}
}
- (CGSize)yb_textSizeWithAttributedString:(NSAttributedString *)attributedString width:(float)width numberOfLines:(NSInteger)numberOfLines
{
@autoreleasepool {
UILabel *sizeLabel = [[UILabel alloc] initWithFrame:CGRectZero];
sizeLabel.numberOfLines = numberOfLines;
sizeLabel.attributedText = attributedString;
CGSize fitSize = [sizeLabel sizeThatFits:CGSizeMake(width, MAXFLOAT)];
returnfitSize;
}
}
#pragma mark - tapEffect
- (void)yb_tapEffectWithStatus:(BOOL)status
{
if(self.isTapEffect) {
NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithAttributedString:self.attributedText];
NSMutableAttributedString *subAtt = [[NSMutableAttributedString alloc] initWithAttributedString:[[self.effectDic allValues] firstObject]];
NSRange range = NSRangeFromString([[self.effectDic allKeys] firstObject]);
if(status) {
[subAtt addAttribute:NSBackgroundColorAttributeName value:self.tapHighlightedColor range:NSMakeRange(0, subAtt.string.length)];
[attStr replaceCharactersInRange:range withAttributedString:subAtt];
}else{
[attStr replaceCharactersInRange:range withAttributedString:subAtt];
}
self.attributedText = attStr;
}
}
- (void)yb_saveEffectDicWithRange:(NSRange)range
{
self.effectDic = [NSMutableDictionary dictionary];
NSAttributedString *subAttribute = [self.attributedText attributedSubstringFromRange:range];
[self.effectDic setObject:subAttribute forKey:NSStringFromRange(range)];
}
#pragma mark - getRange
- (void)yb_getRangesWithStrings:(NSArray *)strings
{
if(self.attributedText ==nil) {
self.isTapAction =NO;
return;
}
self.isTapAction =YES;
self.isTapEffect =YES;
__block NSString *totalStr =self.attributedText.string;
self.attributeStrings = [NSMutableArray array];
__weak typeof(self) weakSelf = self;
[strings enumerateObjectsUsingBlock:^(NSString *_Nonnullobj, NSUInteger idx,BOOL*_Nonnullstop) {
NSRange range = [totalStr rangeOfString:obj];
if(range.length !=0) {
totalStr = [totalStr stringByReplacingCharactersInRange:range withString:[weakSelf yb_getStringWithRange:range]];
YBAttributeModel *model = [YBAttributeModel new];
model.range = range;
model.str = obj;
[weakSelf.attributeStrings addObject:model];
}
}];
}
- (void)yb_getRangesWithRanges:(NSArray *)ranges
{
if(self.attributedText ==nil) {
self.isTapAction =NO;
return;
}
self.isTapAction =YES;
self.isTapEffect =YES;
__block NSString *totalStr =self.attributedText.string;
self.attributeStrings = [NSMutableArray array];
__weak typeof(self) weakSelf = self;
[ranges enumerateObjectsUsingBlock:^(NSString *_Nonnullobj, NSUInteger idx,BOOL*_Nonnullstop) {
NSRange range = NSRangeFromString(obj);
NSAssert(totalStr.length >= range.location + range.length,@"NSRange(%ld,%ld) is out of bounds",range.location,range.length);
NSString * string = [totalStr substringWithRange:range];
YBAttributeModel *model = [YBAttributeModel new];
model.range = range;
model.str = string;
[weakSelf.attributeStrings addObject:model];
}];
}
- (NSString *)yb_getStringWithRange:(NSRange)range
{
NSMutableString *string = [NSMutableString string];
for(inti =0; i < range.length ; i++) {
[string appendString:@" "];
}
returnstring;
}
#pragma mark - KVO
- (void)yb_addObserver
{
[selfaddObserver:selfforKeyPath:@"attributedText"options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil];
}
- (void)yb_removeObserver
{
idinfo =self.observationInfo;
NSString * key =@"attributedText";
NSArray *array = [info valueForKey:@"_observances"];
for(idobjcinarray) {
idProperties = [objc valueForKeyPath:@"_property"];
NSString *keyPath = [Properties valueForKeyPath:@"_keyPath"];
if([key isEqualToString:keyPath]) {
[selfremoveObserver:selfforKeyPath:@"attributedText"context:nil];
}
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionaryid> *)change context:(void*)context
{
if([keyPath isEqualToString:@"attributedText"]) {
if(self.isTapAction) {
if(![change[NSKeyValueChangeNewKey] isEqual: change[NSKeyValueChangeOldKey]]) {
}
}
}
}
@end
```