Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ASTextNode2] Add initial implementation for link handling. #396

Merged
merged 3 commits into from
Jul 3, 2017
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 84 additions & 2 deletions Source/Private/ASTextNode2.mm
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,54 @@ - (id)_linkAttributeValueAtPoint:(CGPoint)point
inAdditionalTruncationMessage:(out BOOL *)inAdditionalTruncationMessageOut
forHighlighting:(BOOL)highlighting
{
AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE();
ASDN::MutexLocker l(__instanceLock__);

ASTextContainer *containerCopy = [_textContainer copy];
containerCopy.size = self.calculatedSize;
ASTextLayout *layout = [ASTextNode2 compatibleLayoutWithContainer:containerCopy text:_attributedText];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's required for us to copy the container here (or below at line 740). Except in extreme edge cases, _textContainer will be at the displayed size. Is this just extra safety, or was this necessary to make it work?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Adlai-Holler unfortunately, without these, link handling does not work. I could try removing just one or the other, but instead I think it is going to require a more detailed look at what is going on.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK would you be willing to add a comment linking to this discussion?

NSRange visibleRange = layout.visibleRange;
NSRange clampedRange = NSIntersectionRange(visibleRange, NSMakeRange(0, _attributedText.length));

ASTextRange *range = [layout closestTextRangeAtPoint:point];

// For now, assume that a tap inside this text, but outside the text range is a tap on the
// truncation token.
if (![layout textRangeAtPoint:point]) {
*inAdditionalTruncationMessageOut = YES;
return nil;
}

NSRange effectiveRange = NSMakeRange(0, 0);
for (NSString *attributeName in self.linkAttributeNames) {
id value = [self.attributedText attribute:attributeName atIndex:range.start.offset longestEffectiveRange:&effectiveRange inRange:clampedRange];
NSString *name = attributeName;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: remove extra local variable or make the difference between name and attributeName more clear.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Doing this required adding a semi-ugly __strong to the iteration variable declaration, as the compiler does generate an error for writing to that variable without it.

if (value == nil || name == nil) {
// Didn't find anything
continue;
}

// If highlighting, check with delegate first. If not implemented, assume YES.
if (highlighting
&& [_delegate respondsToSelector:@selector(textNode:shouldHighlightLinkAttribute:value:atPoint:)]
&& ![_delegate textNode:(ASTextNode *)self shouldHighlightLinkAttribute:name value:value atPoint:point]) {
value = nil;
name = nil;
}

if (value != nil || name != nil) {
*rangeOut = effectiveRange;
if (NSMaxRange(*rangeOut) > NSMaxRange(visibleRange)) {
(*rangeOut).length = MAX(NSMaxRange(visibleRange) - (*rangeOut).location, 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it reasonable for this just to be *rangeOut = NSIntersectionRange(effectiveRange, visibleRange) or am I missing something?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion, done.

}

if (attributeNameOut != NULL) {
*attributeNameOut = name;
}

return value;
}
}

return nil;
}

Expand Down Expand Up @@ -560,8 +607,13 @@ - (void)setHighlightRange:(NSRange)highlightRange animated:(BOOL)animated

- (void)_setHighlightRange:(NSRange)highlightRange forAttributeName:(NSString *)highlightedAttributeName value:(id)highlightedAttributeValue animated:(BOOL)animated
{
// Set these so that link tapping works.
_highlightedLinkAttributeName = highlightedAttributeName;
_highlightedLinkAttributeValue = highlightedAttributeValue;

AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE();
// Much of the code from original ASTextNode is probably usable here.

return;
}

Expand Down Expand Up @@ -665,7 +717,37 @@ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
ASDisplayNodeAssertMainThread();

[super touchesBegan:touches withEvent:event];
AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE();

CGPoint point = [[touches anyObject] locationInView:self.view];

NSRange range = NSMakeRange(0, 0);
NSString *linkAttributeName = nil;
BOOL inAdditionalTruncationMessage = NO;

id linkAttributeValue = [self _linkAttributeValueAtPoint:point
attributeName:&linkAttributeName
range:&range
inAdditionalTruncationMessage:&inAdditionalTruncationMessage
forHighlighting:YES];

NSUInteger lastCharIndex = NSIntegerMax;
BOOL linkCrossesVisibleRange = (lastCharIndex > range.location) && (lastCharIndex < NSMaxRange(range) - 1);

if (inAdditionalTruncationMessage) {
NSRange visibleRange = NSMakeRange(0, 0);
{
ASDN::MutexLocker l(__instanceLock__);
ASTextContainer *containerCopy = [_textContainer copy];
containerCopy.size = self.calculatedSize;
ASTextLayout *layout = [ASTextNode2 compatibleLayoutWithContainer:containerCopy text:_attributedText];
visibleRange = layout.visibleRange;
}
NSRange truncationMessageRange = [self _additionalTruncationMessageRangeWithVisibleRange:visibleRange];
[self _setHighlightRange:truncationMessageRange forAttributeName:ASTextNodeTruncationTokenAttributeName value:nil animated:YES];
} else if (range.length && !linkCrossesVisibleRange && linkAttributeValue != nil && linkAttributeName != nil) {
[self _setHighlightRange:range forAttributeName:linkAttributeName value:linkAttributeValue animated:YES];
}

return;
}

Expand Down