diff --git a/CHANGELOG.md b/CHANGELOG.md index 5540678cc..86b62868f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [ASTextNode2] Add initial implementation for link handling. [Scott Goodson](https://github.com/appleguy) [#396](https://github.com/TextureGroup/Texture/pull/396) ##2.3.4 - [Yoga] Rewrite YOGA_TREE_CONTIGUOUS mode with improved behavior and cleaner integration [Scott Goodson](https://github.com/appleguy) diff --git a/Source/Private/ASTextNode2.mm b/Source/Private/ASTextNode2.mm index 379e126b0..d266d6cca 100644 --- a/Source/Private/ASTextNode2.mm +++ b/Source/Private/ASTextNode2.mm @@ -488,7 +488,52 @@ - (id)_linkAttributeValueAtPoint:(CGPoint)point inAdditionalTruncationMessage:(out BOOL *)inAdditionalTruncationMessageOut forHighlighting:(BOOL)highlighting { - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); + ASDN::MutexLocker l(__instanceLock__); + + // TODO: The copy and application of size shouldn't be required, but it is currently. + // See discussion in https://github.com/TextureGroup/Texture/pull/396 + ASTextContainer *containerCopy = [_textContainer copy]; + containerCopy.size = self.calculatedSize; + ASTextLayout *layout = [ASTextNode2 compatibleLayoutWithContainer:containerCopy text:_attributedText]; + 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 (__strong NSString *attributeName in self.linkAttributeNames) { + id value = [self.attributedText attribute:attributeName atIndex:range.start.offset longestEffectiveRange:&effectiveRange inRange:clampedRange]; + if (value == nil) { + // Didn't find any links specified with this attribute. + 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:attributeName value:value atPoint:point]) { + value = nil; + attributeName = nil; + } + + if (value != nil || attributeName != nil) { + *rangeOut = NSIntersectionRange(visibleRange, effectiveRange); + + if (attributeNameOut != NULL) { + *attributeNameOut = attributeName; + } + + return value; + } + } + return nil; } @@ -560,8 +605,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; } @@ -665,7 +715,39 @@ - (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__); + // TODO: The copy and application of size shouldn't be required, but it is currently. + // See discussion in https://github.com/TextureGroup/Texture/pull/396 + 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; }