Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
7 changes: 7 additions & 0 deletions Sources/MPILabel.h
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,13 @@ Whether or not the text is truncated. It's expensive if text not rendered.
*/
@property (nonatomic) BOOL fadeOnAsynchronouslyDisplay;

/**
Additional interactive area when to didInteractWithLink

The default value is UIEdgeInsetsZero. negative values ​​are not allowed.
*/
@property (nonatomic) UIEdgeInsets interactionLinkPointExpansionInsets;

/// To show menu by selectedRange.
- (void)showMenu;

Expand Down
7 changes: 6 additions & 1 deletion Sources/MPILabel.m
Original file line number Diff line number Diff line change
Expand Up @@ -695,11 +695,16 @@ - (NSRange)linkRangeAtPoint:(CGPoint)point inTruncation:(nullable BOOL *)inTrunc

point = [self convertPointToTextKit:point forBounds:self.bounds textSize:renderer.size];

UIEdgeInsets expansion = self.interactionLinkPointExpansionInsets;
NSRange linkRange;
#ifdef DEBUG
MPITextLink *link =
#endif
[renderer attribute:MPITextLinkAttributeName atPoint:point effectiveRange:&linkRange inTruncation:inTruncation];
[renderer attribute:MPITextLinkAttributeName
atPoint:point
expansion:expansion
effectiveRange:&linkRange
inTruncation:inTruncation];
#ifdef DEBUG
if (linkRange.location != NSNotFound) {
NSAssert([link isKindOfClass:MPITextLink.class], @"The value for MPITextLinkAttributeName must be of type MPITextLink.");
Expand Down
2 changes: 2 additions & 0 deletions Sources/MPITextRenderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,14 @@ NS_ASSUME_NONNULL_BEGIN

@param name The name of an attribute.
@param point The index at which to test for attributeName.
@param expansion The point expands outwards to form a rectangle.
@param effectiveRange If the named attribute does not exist at index, the range is (NSNotFound, 0).
@param inTruncation Indicates the attribute is in truncation.
@return Returns The value for the attribute named attributeName of the character at index, or nil if there is no such attribute.
*/
- (nullable id)attribute:(NSAttributedStringKey)name
atPoint:(CGPoint)point
expansion:(UIEdgeInsets)expansion
effectiveRange:(nullable NSRangePointer)effectiveRange
inTruncation:(nullable BOOL *)inTruncation;

Expand Down
84 changes: 61 additions & 23 deletions Sources/MPITextRenderer.m
Original file line number Diff line number Diff line change
Expand Up @@ -241,45 +241,83 @@ @implementation MPITextRenderer (MPITextKitExtendedRenderer)

- (nullable id)attribute:(NSAttributedStringKey)name
atPoint:(CGPoint)point
expansion:(UIEdgeInsets)expansion
effectiveRange:(nullable NSRangePointer)effectiveRange
inTruncation:(BOOL *)pInTruncation {
inTruncation:(nullable BOOL *)inTruncation {
__block id value = nil;
__block NSRange attributeRange = NSMakeRange(NSNotFound, 0);
__block BOOL inTruncation;
__block NSRange pEffectiveRange = NSMakeRange(NSNotFound, 0);
__block BOOL pInTruncation = NO;
NSValue *truncationCharacterRange = self.truncationInfo.truncationCharacterRange;
CGRect boundingRect = CGRectMake(point.x - expansion.left,
point.y - expansion.top,
expansion.left + expansion.right,
expansion.top + expansion.bottom);
[self.context performBlockWithLockedTextKitComponents:^(MPITextLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
// Find the range.
NSRange visibleGlyphsRange = [layoutManager glyphRangeForBoundingRect:(CGRect){ .size = textContainer.size }
inTextContainer:textContainer];
NSRange visibleGlyphsRange = [layoutManager glyphRangeForBoundingRect:boundingRect inTextContainer:textContainer];
NSRange visibleCharactersRange = [layoutManager characterRangeForGlyphRange:visibleGlyphsRange actualGlyphRange:NULL];
NSUInteger glyphIndex = [layoutManager glyphIndexForPoint:point inTextContainer:textContainer];
if (glyphIndex != NSNotFound) {
CGRect glyphRect = [layoutManager boundingRectForGlyphRange:NSMakeRange(glyphIndex, 1) inTextContainer:textContainer];
if (CGRectContainsPoint(glyphRect, point)) {
NSUInteger characterIndex = [layoutManager characterIndexForGlyphAtIndex:glyphIndex];
value = [textStorage attribute:name atIndex:characterIndex longestEffectiveRange:&attributeRange inRange:visibleCharactersRange];
if (!value) {
attributeRange = NSMakeRange(NSNotFound, 0);

__block NSUInteger characterIndex = NSNotFound;
if (visibleCharactersRange.length > 0) {
__block CGFloat minDistance = CGFLOAT_MAX;
[textStorage enumerateAttribute:name
inRange:visibleCharactersRange
options:0
usingBlock:^(id _Nullable attrValue, NSRange attrRange, BOOL *attrStop) {
if (attrValue == nil) {
return;
}
NSRange attrGlyphRange = [layoutManager glyphRangeForCharacterRange:attrRange actualCharacterRange:NULL];
[layoutManager enumerateEnclosingRectsForGlyphRange:attrGlyphRange
withinSelectedGlyphRange:NSMakeRange(NSNotFound, 0)
inTextContainer:textContainer
usingBlock:^(CGRect attRect, BOOL *innerStop) {
if (!CGRectIntersectsRect(attRect, boundingRect)) {
return;
}

CGFloat dx = MAX(MAX(CGRectGetMinX(attRect) - point.x, 0), point.x - CGRectGetMaxX(attRect));
CGFloat dy = MAX(MAX(CGRectGetMinY(attRect) - point.y, 0), point.y - CGRectGetMaxY(attRect));
CGFloat distance = sqrt(dx * dx + dy * dy);

if (distance < minDistance) {
minDistance = distance;
characterIndex = attrRange.location;
}
}];
}];
} else {
NSUInteger glyphIndex = [layoutManager glyphIndexForPoint:point inTextContainer:textContainer];
if (glyphIndex != NSNotFound) {
CGRect glyphRect = [layoutManager boundingRectForGlyphRange:NSMakeRange(glyphIndex, 1) inTextContainer:textContainer];
if (CGRectContainsPoint(glyphRect, point)) {
characterIndex = [layoutManager characterIndexForGlyphAtIndex:glyphIndex];
}
}
}
if (characterIndex != NSNotFound) {
NSRange fullGlyphsRange = [layoutManager glyphRangeForBoundingRect:(CGRect){ .size = textContainer.size } inTextContainer:textContainer];
NSRange fullCharactersRange = [layoutManager characterRangeForGlyphRange:fullGlyphsRange actualGlyphRange:NULL];
value = [textStorage attribute:name atIndex:characterIndex longestEffectiveRange:&pEffectiveRange inRange:fullCharactersRange];
}
if (!value) {
pEffectiveRange = NSMakeRange(NSNotFound, 0);
}

// Check that the range is in truncation.
if (truncationCharacterRange &&
NSLocationInRange(attributeRange.location, truncationCharacterRange.rangeValue)) {
inTruncation = YES;
attributeRange = NSMakeRange(attributeRange.location - truncationCharacterRange.rangeValue.location, attributeRange.length);
if (truncationCharacterRange && NSLocationInRange(pEffectiveRange.location, truncationCharacterRange.rangeValue)) {
pInTruncation = YES;
pEffectiveRange = NSMakeRange(pEffectiveRange.location - truncationCharacterRange.rangeValue.location, pEffectiveRange.length);
} else {
inTruncation = NO;
pInTruncation = NO;
}
}];

if (effectiveRange) {
*effectiveRange = attributeRange;
*effectiveRange = pEffectiveRange;
}

if (pInTruncation) {
*pInTruncation = inTruncation;
if (inTruncation) {
*inTruncation = pInTruncation;
}

return value;
Expand All @@ -288,7 +326,7 @@ - (nullable id)attribute:(NSAttributedStringKey)name
- (NSUInteger)characterIndexForPoint:(CGPoint)point {
__block NSUInteger characterIndex = NSNotFound;
[self.context performBlockWithLockedTextKitComponents:^(MPITextLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
NSUInteger glyphIndex = [layoutManager glyphIndexForPoint:point inTextContainer:textContainer];
NSUInteger glyphIndex = [layoutManager glyphIndexForPoint:point inTextContainer:textContainer];
if (glyphIndex != NSNotFound) {
characterIndex = [layoutManager characterIndexForGlyphAtIndex:glyphIndex];
}
Expand Down
Loading