fix(ios): tapping on span does not emit linkTap event (#10028)

This commit is contained in:
Dimitris-Rafail Katsampas
2022-09-30 18:04:20 +03:00
committed by GitHub
parent bdade0f0d5
commit ab3416d4d0
2 changed files with 61 additions and 20 deletions

View File

@@ -85,6 +85,7 @@
} ];
}
BOOL isLabel = [self isKindOfClass:[UILabel class]];
if (lineHeight > 0) {
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineSpacing = lineHeight;
@@ -92,10 +93,11 @@
if ([self isKindOfClass:[UIButton class]]) {
paragraphStyle.alignment = ((UIButton*)self).titleLabel.textAlignment;
} else {
// Paragraph alignment is also important for tappable spans as NSTextContainer takes it into account
paragraphStyle.alignment = ((UILabel*)self).textAlignment;
}
if ([self isKindOfClass:[UILabel class]]) {
if (isLabel) {
// make sure a possible previously set line break mode is not lost when line height is specified
paragraphStyle.lineBreakMode = ((UILabel*)self).lineBreakMode;
}
@@ -103,9 +105,15 @@
0,
attrText.length
}];
} else if ([self isKindOfClass:[UITextView class]]) {
} else if (isLabel || [self isKindOfClass:[UITextView class]]) {
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
if (isLabel) {
// It's important to set paragraph alignment for link tap to work on multi-line spans as NSTextContainer takes it into account
paragraphStyle.alignment = ((UILabel*)self).textAlignment;
} else {
paragraphStyle.alignment = ((UITextView*)self).textAlignment;
}
[attrText addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:(NSRange){
0,
attrText.length

View File

@@ -33,8 +33,23 @@ class UILabelClickHandlerImpl extends NSObject {
public linkTap(tapGesture: UITapGestureRecognizer) {
const owner = this._owner.get();
if (owner) {
// https://stackoverflow.com/a/35789589
const label = <UILabel>owner.nativeTextViewProtected;
// This offset along with setting paragraph style alignment will achieve perfect horizontal alignment for NSTextContainer
let offsetXMultiplier: number;
switch (owner.textAlignment) {
case 'center':
offsetXMultiplier = 0.5;
break;
case 'right':
offsetXMultiplier = 1.0;
break;
default:
offsetXMultiplier = 0.0;
break;
}
const offsetYMultiplier: number = 0.5; // Text is vertically aligned to center
const layoutManager = NSLayoutManager.alloc().init();
const textContainer = NSTextContainer.alloc().initWithSize(CGSizeZero);
const textStorage = NSTextStorage.alloc().initWithAttributedString(owner.nativeTextViewProtected['attributedText']);
@@ -51,14 +66,30 @@ class UILabelClickHandlerImpl extends NSObject {
const locationOfTouchInLabel = tapGesture.locationInView(label);
const textBoundingBox = layoutManager.usedRectForTextContainer(textContainer);
const textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x, (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y);
const textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * offsetXMultiplier - textBoundingBox.origin.x, (labelSize.height - textBoundingBox.size.height) * offsetYMultiplier - textBoundingBox.origin.y);
const locationOfTouchInTextContainer = CGPointMake(locationOfTouchInLabel.x - textContainerOffset.x, locationOfTouchInLabel.y - textContainerOffset.y);
const indexOfCharacter = layoutManager.characterIndexForPointInTextContainerFractionOfDistanceBetweenInsertionPoints(locationOfTouchInTextContainer, textContainer, null);
// Check if tap was inside text bounding rect
if (CGRectContainsPoint(textBoundingBox, locationOfTouchInTextContainer)) {
// According to Apple docs, if no glyph is under point, the nearest glyph is returned
const glyphIndex = layoutManager.glyphIndexForPointInTextContainerFractionOfDistanceThroughGlyph(locationOfTouchInTextContainer, textContainer, null);
// In order to determine whether the tap point actually lies within the bounds
// of the glyph returned, we call the method below and test
// whether the point falls in the rectangle returned by that method
const glyphRect = layoutManager.boundingRectForGlyphRangeInTextContainer(
{
location: glyphIndex,
length: 1,
},
textContainer
);
// Ensure that an actual glyph was tapped
if (CGRectContainsPoint(glyphRect, locationOfTouchInTextContainer)) {
const indexOfCharacter = layoutManager.characterIndexForGlyphAtIndex(glyphIndex);
let span: Span = null;
// try to find the corresponding span using the spanRanges
// Try to find the corresponding span using the spanRanges
for (let i = 0; i < owner._spanRanges.length; i++) {
const range = owner._spanRanges[i];
if (range.location <= indexOfCharacter && range.location + range.length > indexOfCharacter) {
@@ -75,6 +106,8 @@ class UILabelClickHandlerImpl extends NSObject {
}
}
}
}
}
public static ObjCExposedMethods = {
linkTap: { returns: interop.types.void, params: [interop.types.id] },