Skip to content

Commit

Permalink
Add support for defining explicit links in NSAttributedString
Browse files Browse the repository at this point in the history
  • Loading branch information
Joe Szymanski committed May 8, 2013
1 parent 15120ae commit eda52d4
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 18 deletions.
2 changes: 2 additions & 0 deletions src/attributedlabel/src/NIAttributedLabel.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ typedef enum {
NIVerticalTextAlignmentBottom,
} NIVerticalTextAlignment;

extern NSString * const kNILinkAttributeName;

@protocol NIAttributedLabelDelegate;

/**
Expand Down
79 changes: 61 additions & 18 deletions src/attributedlabel/src/NIAttributedLabel.m
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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];
}
}
Expand All @@ -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];
}
}
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand All @@ -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);
}
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down
1 change: 1 addition & 0 deletions src/attributedlabel/src/NimbusAttributedLabel.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ label.text = @"Nimbus";
* @attention NIAttributedLabel is not designed to detect html anchor tags (i.e. &lt;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"
*
Expand Down

0 comments on commit eda52d4

Please sign in to comment.