From eda52d4f3c0eae53595ff85c00ba8283a3c7a15d Mon Sep 17 00:00:00 2001 From: Joe Szymanski Date: Wed, 8 May 2013 10:54:20 -0400 Subject: [PATCH] Add support for defining explicit links in NSAttributedString --- src/attributedlabel/src/NIAttributedLabel.h | 2 + src/attributedlabel/src/NIAttributedLabel.m | 79 ++++++++++++++----- .../src/NimbusAttributedLabel.h | 1 + 3 files changed, 64 insertions(+), 18 deletions(-) diff --git a/src/attributedlabel/src/NIAttributedLabel.h b/src/attributedlabel/src/NIAttributedLabel.h index fbca7051a..ae5e26e76 100644 --- a/src/attributedlabel/src/NIAttributedLabel.h +++ b/src/attributedlabel/src/NIAttributedLabel.h @@ -54,6 +54,8 @@ typedef enum { NIVerticalTextAlignmentBottom, } NIVerticalTextAlignment; +extern NSString * const kNILinkAttributeName; + @protocol NIAttributedLabelDelegate; /** diff --git a/src/attributedlabel/src/NIAttributedLabel.m b/src/attributedlabel/src/NIAttributedLabel.m index c889ddec7..0abe99985 100644 --- a/src/attributedlabel/src/NIAttributedLabel.m +++ b/src/attributedlabel/src/NIAttributedLabel.m @@ -27,7 +27,7 @@ static const CGFloat kVMargin = 5.0f; static const NSTimeInterval kLongPressTimeInterval = 0.5; static const CGFloat kLongPressGutter = 22; -static NSString* const kLinkAttributedName = @"NIAttributedLabel:Link"; +NSString * const kNILinkAttributeName = @"NIAttributedLabel:Link"; CGFloat ImageDelegateGetAscentCallback(void* refCon); CGFloat ImageDelegateGetDescentCallback(void* refCon); @@ -303,6 +303,27 @@ - (void)setAttributedString:(NSAttributedString *)attributedText { // Remove all images. self.images = nil; + // Pull any explicit links from the attributed string itself + __block NSMutableArray *links = [NSMutableArray array]; + [self.mutableAttributedString enumerateAttribute:kNILinkAttributeName + inRange:NSMakeRange(0, self.mutableAttributedString.length) + options:0 + usingBlock:^(id value, NSRange range, BOOL *stop) { + if (value != nil) + { + if ([value isKindOfClass:[NSURL class]]) + { + [links addObject:[NSTextCheckingResult linkCheckingResultWithRange:range URL:value]]; + } + else if ([value isKindOfClass:[NSString class]]) + { + NSURL *url = [NSURL URLWithString:value]; + [links addObject:[NSTextCheckingResult linkCheckingResultWithRange:range URL:url]]; + } + } + }]; + self.explicitLinkLocations = links; + [self attributedTextDidChange]; } } @@ -322,6 +343,27 @@ - (void)setAttributedText:(NSAttributedString *)attributedText { // Remove all images. self.images = nil; + // Pull any explicit links from the attributed string itself + __block NSMutableArray *links = [NSMutableArray array]; + [self.mutableAttributedString enumerateAttribute:kNILinkAttributeName + inRange:NSMakeRange(0, self.mutableAttributedString.length) + options:0 + usingBlock:^(id value, NSRange range, BOOL *stop) { + if (value != nil) + { + if ([value isKindOfClass:[NSURL class]]) + { + [links addObject:[NSTextCheckingResult linkCheckingResultWithRange:range URL:value]]; + } + else if ([value isKindOfClass:[NSString class]]) + { + NSURL *url = [NSURL URLWithString:value]; + [links addObject:[NSTextCheckingResult linkCheckingResultWithRange:range URL:url]]; + } + } + }]; + self.explicitLinkLocations = links; + [self attributedTextDidChange]; } } @@ -788,15 +830,15 @@ - (NSTextCheckingResult *)linkAtPoint:(CGPoint)point { CGPoint relativePoint = CGPointMake(point.x-CGRectGetMinX(rect), point.y-CGRectGetMinY(rect)); CFIndex idx = CTLineGetStringIndexForPosition(line, relativePoint); - + NSUInteger offset = 0; for (NIAttributedLabelImage *labelImage in self.images) { if (labelImage.index < idx) { offset++; } - + } - + foundLink = [self linkAtIndex:idx - offset];; if (foundLink) { NSTextCheckingResult *result = [NSTextCheckingResult linkCheckingResultWithRange:NSMakeRange(foundLink.range.location + offset, foundLink.range.length) URL:foundLink.URL]; @@ -1129,8 +1171,9 @@ - (void)_applyLinkStyleWithResults:(NSArray *)results toAttributedString:(NSMuta // We add a no-op attribute in order to force a run to exist for each link. Otherwise the // runCount will be one in this line, causing the entire line to be highlighted rather than // just the link when when no special attributes are set. - [attributedString addAttribute:kLinkAttributedName - value:[NSNumber numberWithBool:YES] + [attributedString removeAttribute:kNILinkAttributeName range:result.range]; + [attributedString addAttribute:kNILinkAttributeName + value:result.URL range:result.range]; if (self.linksHaveUnderlines) { @@ -1185,20 +1228,20 @@ - (NSMutableAttributedString *)mutableAttributedStringWithAdditions { callbacks.getAscent = ImageDelegateGetAscentCallback; callbacks.getDescent = ImageDelegateGetDescentCallback; callbacks.getWidth = ImageDelegateGetWidthCallback; - + NSUInteger index = labelImage.index; if (index >= attributedString.length) { index = attributedString.length - 1; } - + NSDictionary *attributes = [attributedString attributesAtIndex:index effectiveRange:NULL]; CTFontRef font = (__bridge CTFontRef)[attributes valueForKey:(__bridge id)kCTFontAttributeName]; - + if (font != NULL) { labelImage.fontAscent = CTFontGetAscent(font); labelImage.fontDescent = CTFontGetDescent(font); } - + CTRunDelegateRef delegate = CTRunDelegateCreate(&callbacks, (__bridge void *)labelImage); // Character to use as recommended by kCTRunDelegateAttributeName documentation. @@ -1271,9 +1314,9 @@ - (void)drawImages { NULL); CGFloat imageBoxHeight = labelImage.boxSize.height; - + CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, nil); - + CGFloat imageBoxOriginY = 0.0f; switch (labelImage.verticalTextAlignment) { case NIVerticalTextAlignmentTop: @@ -1286,13 +1329,13 @@ - (void)drawImages { imageBoxOriginY = lineBottomY; break; } - + CGRect rect = CGRectMake(lineOrigin.x + xOffset, imageBoxOriginY, width, imageBoxHeight); UIEdgeInsets flippedMargins = labelImage.margins; CGFloat top = flippedMargins.top; flippedMargins.top = flippedMargins.bottom; flippedMargins.bottom = top; - + CGRect imageRect = UIEdgeInsetsInsetRect(rect, flippedMargins); CGContextDrawImage(ctx, imageRect, labelImage.image.CGImage); } @@ -1592,14 +1635,14 @@ - (void)actionSheetCancel:(UIActionSheet *)actionSheet { /////////////////////////////////////////////////////////////////////////////////////////////////// CGFloat ImageDelegateGetAscentCallback(void* refCon) { NIAttributedLabelImage *labelImage = (__bridge NIAttributedLabelImage *)refCon; - + switch (labelImage.verticalTextAlignment) { case NIVerticalTextAlignmentMiddle: { CGFloat ascent = labelImage.fontAscent; CGFloat descent = labelImage.fontDescent; CGFloat baselineFromMid = (ascent + descent) / 2 - descent; - + return labelImage.boxSize.height / 2 + baselineFromMid; } case NIVerticalTextAlignmentTop: @@ -1614,14 +1657,14 @@ CGFloat ImageDelegateGetAscentCallback(void* refCon) { /////////////////////////////////////////////////////////////////////////////////////////////////// CGFloat ImageDelegateGetDescentCallback(void* refCon) { NIAttributedLabelImage *labelImage = (__bridge NIAttributedLabelImage *)refCon; - + switch (labelImage.verticalTextAlignment) { case NIVerticalTextAlignmentMiddle: { CGFloat ascent = labelImage.fontAscent; CGFloat descent = labelImage.fontDescent; CGFloat baselineFromMid = (ascent + descent) / 2 - descent; - + return labelImage.boxSize.height / 2 - baselineFromMid; } case NIVerticalTextAlignmentTop: diff --git a/src/attributedlabel/src/NimbusAttributedLabel.h b/src/attributedlabel/src/NimbusAttributedLabel.h index 5a83f0605..1b953a45b 100644 --- a/src/attributedlabel/src/NimbusAttributedLabel.h +++ b/src/attributedlabel/src/NimbusAttributedLabel.h @@ -96,6 +96,7 @@ label.text = @"Nimbus"; * @attention NIAttributedLabel is not designed to detect html anchor tags (i.e. <a>). If * you want to attach a URL to a given range of text you must use * @link NIAttributedLabel::addLink:range: addLink:range:@endlink. + * You can add links to the attributed string using the attribute kNILinkAttributeName. * * @image html NIAttributedLabel_autoDetectLinksOff.png "Before enabling autoDetectLinks" *