123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540 |
- //
- // UILabel+YBAttributeTextTapAction.m
- //
- // Created by LYB on 16/7/1.
- // Copyright © 2016年 LYB. All rights reserved.
- //
- #import "UILabel+YBAttributeTextTapAction.h"
- #import <objc/runtime.h>
- #import <CoreText/CoreText.h>
- #import <Foundation/Foundation.h>
- @interface YBAttributeModel : NSObject
- @property (nonatomic, copy) NSString *str;
- @property (nonatomic) NSRange range;
- @end
- @implementation YBAttributeModel
- @end
- @implementation UILabel (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
- {
- return objc_getAssociatedObject(self, _cmd);
- }
- - (void)setTapBlock:(void (^)(UILabel *, NSString *, NSRange, NSInteger))tapBlock
- {
- objc_setAssociatedObject(self, @selector(tapBlock), tapBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
- }
- - (id<YBAttributeTapActionDelegate>)delegate
- {
- return objc_getAssociatedObject(self, _cmd);
- }
- - (void)setDelegate:(id<YBAttributeTapActionDelegate>)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);
- }
- return color;
- }
- - (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 <NSString *> *)strings tapClicked:(void (^) (UILabel * label, NSString *string, NSRange range, NSInteger index))tapClick
- {
- [self yb_removeAttributeTapActions];
- [self yb_getRangesWithStrings:strings];
- self.userInteractionEnabled = YES;
-
- if (self.tapBlock != tapClick) {
- self.tapBlock = tapClick;
- }
- }
- - (void)yb_addAttributeTapActionWithStrings:(NSArray <NSString *> *)strings
- delegate:(id <YBAttributeTapActionDelegate> )delegate
- {
- [self yb_removeAttributeTapActions];
- [self yb_getRangesWithStrings:strings];
- self.userInteractionEnabled = YES;
-
- if (self.delegate != delegate) {
- self.delegate = delegate;
- }
- }
- - (void)yb_addAttributeTapActionWithRanges:(NSArray<NSString *> *)ranges tapClicked:(void (^)(UILabel *, NSString *, NSRange, NSInteger))tapClick
- {
- [self yb_removeAttributeTapActions];
- [self yb_getRangesWithRanges:ranges];
- self.userInteractionEnabled = YES;
-
- if (self.tapBlock != tapClick) {
- self.tapBlock = tapClick;
- }
- }
- - (void)yb_addAttributeTapActionWithRanges:(NSArray<NSString *> *)ranges delegate:(id<YBAttributeTapActionDelegate>)delegate
- {
- [self yb_removeAttributeTapActions];
- [self yb_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<UITouch *> *)touches withEvent:(UIEvent *)event
- {
- if (!self.isTapAction) {
- [super touchesBegan: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;
-
- BOOL ret = [self yb_getTapFrameWithTouchPoint:point result:^(NSString *string, NSRange range, NSInteger index) {
-
- if (weakSelf.isTapEffect) {
-
- [weakSelf yb_saveEffectDicWithRange:range];
-
- [weakSelf yb_tapEffectWithStatus:YES];
- }
-
- }];
- if (!ret) {
- [super touchesBegan:touches withEvent:event];
- }
- }
- - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
- {
- if (!self.isTapAction) {
- [super touchesEnded:touches withEvent:event];
- return;
- }
- if (self.isTapEffect) {
- [self performSelectorOnMainThread:@selector(yb_tapEffectWithStatus:) withObject:nil waitUntilDone:NO];
- }
-
- UITouch *touch = [touches anyObject];
-
- CGPoint point = [touch locationInView:self];
-
- __weak typeof(self) weakSelf = self;
-
- BOOL ret = [self yb_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) {
- [super touchesEnded:touches withEvent:event];
- }
- }
- - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
- {
- if (!self.isTapAction) {
- [super touchesCancelled:touches withEvent:event];
- return;
- }
- if (self.isTapEffect) {
- [self performSelectorOnMainThread:@selector(yb_tapEffectWithStatus:) withObject:nil waitUntilDone:NO];
- }
- UITouch *touch = [touches anyObject];
-
- CGPoint point = [touch locationInView:self];
-
- __weak typeof(self) weakSelf = self;
-
- BOOL ret = [self yb_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) {
- [super touchesCancelled: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 = [self yb_textSizeWithAttributedString:self.attributedText width:self.bounds.size.width numberOfLines:0].height;
-
- if (!lines) {
- CFRelease(frame);
- CFRelease(framesetter);
- CGPathRelease(Path);
- return NO;
- }
-
- CFIndex count = CFArrayGetCount(lines);
-
- CGPoint origins[count];
-
- CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), origins);
-
- CGAffineTransform transform = [self yb_transformForCoreText];
-
- for (CFIndex i = 0; i < count; i++) {
- CGPoint linePoint = origins[i];
-
- CTLineRef line = CFArrayGetValueAtIndex(lines, i);
-
- CGRect flippedRect = [self yb_getLineBounds:line point:linePoint];
-
- CGRect rect = CGRectApplyAffineTransform(flippedRect, transform);
-
- CGFloat lineOutSpace = (self.bounds.size.height - total_height) / 2;
-
- rect.origin.y = lineOutSpace + [self yb_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 (int j = 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);
- return YES;
- }
- }
- }
- }
- CFRelease(frame);
- CFRelease(framesetter);
- CGPathRelease(Path);
- return NO;
- }
- - (CGAffineTransform)yb_transformForCoreText
- {
- return CGAffineTransformScale(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 = [self yb_textSizeWithAttributedString:attributedString width:self.bounds.size.width numberOfLines:0].height;
- return CGRectMake(point.x, point.y , width, height);
- }
- - (CGFloat)yb_getLineOrign:(CTLineRef)line
- {
- CFRange range = CTLineGetStringRange(line);
- if (range.location == 0) {
- return 0.;
- }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 [self yb_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)];
- return fitSize;
- }
- }
- #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 <NSString *> *)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 * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
-
- 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 <NSString *> *)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 * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
- 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 (int i = 0; i < range.length ; i++) {
-
- [string appendString:@" "];
- }
- return string;
- }
- #pragma mark - KVO
- - (void)yb_addObserver
- {
- [self addObserver:self forKeyPath:@"attributedText" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil];
- }
- - (void)yb_removeObserver
- {
- id info = self.observationInfo;
- NSString * key = @"attributedText";
- NSArray *array = [info valueForKey:@"_observances"];
- for (id objc in array) {
- id Properties = [objc valueForKeyPath:@"_property"];
- NSString *keyPath = [Properties valueForKeyPath:@"_keyPath"];
- if ([key isEqualToString:keyPath]) {
- [self removeObserver:self forKeyPath:@"attributedText" context:nil];
- }
- }
- }
- - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
- {
- if ([keyPath isEqualToString:@"attributedText"]) {
- if (self.isTapAction) {
- if (![change[NSKeyValueChangeNewKey] isEqual: change[NSKeyValueChangeOldKey]]) {
-
- }
- }
- }
- }
- @end
|