@@ -15,10 +15,101 @@ export * from "./text-base-common";
1515
1616const majorVersion = ios . MajorVersion ;
1717
18+ class UILabelClickHandlerImpl extends NSObject {
19+ private _owner : WeakRef < TextBase > ;
20+
21+ public static initWithOwner ( owner : WeakRef < TextBase > ) : UILabelClickHandlerImpl {
22+ let handler = < UILabelClickHandlerImpl > UILabelClickHandlerImpl . new ( ) ;
23+ handler . _owner = owner ;
24+
25+ return handler ;
26+ }
27+
28+ public linkTap ( tapGesture : UITapGestureRecognizer ) {
29+ let owner = this . _owner . get ( ) ;
30+ if ( owner ) {
31+ // https://stackoverflow.com/a/35789589
32+ let label = < UILabel > owner . nativeTextViewProtected ;
33+ let layoutManager = NSLayoutManager . alloc ( ) . init ( ) ;
34+ let textContainer = NSTextContainer . alloc ( ) . initWithSize ( CGSizeZero ) ;
35+ let textStorage = NSTextStorage . alloc ( ) . initWithAttributedString ( owner . nativeTextViewProtected [ "attributedText" ] ) ;
36+
37+ layoutManager . addTextContainer ( textContainer ) ;
38+ textStorage . addLayoutManager ( layoutManager ) ;
39+
40+ textContainer . lineFragmentPadding = 0 ;
41+ textContainer . lineBreakMode = label . lineBreakMode ;
42+ textContainer . maximumNumberOfLines = label . numberOfLines ;
43+ let labelSize = label . bounds . size ;
44+ textContainer . size = labelSize ;
45+
46+ let locationOfTouchInLabel = tapGesture . locationInView ( label ) ;
47+ let textBoundingBox = layoutManager . usedRectForTextContainer ( textContainer ) ;
48+
49+ let textContainerOffset = CGPointMake ( ( labelSize . width - textBoundingBox . size . width ) * 0.5 - textBoundingBox . origin . x ,
50+ ( labelSize . height - textBoundingBox . size . height ) * 0.5 - textBoundingBox . origin . y ) ;
51+
52+ let locationOfTouchInTextContainer = CGPointMake ( locationOfTouchInLabel . x - textContainerOffset . x ,
53+ locationOfTouchInLabel . y - textContainerOffset . y ) ;
54+
55+ let indexOfCharacter = layoutManager . characterIndexForPointInTextContainerFractionOfDistanceBetweenInsertionPoints (
56+ locationOfTouchInTextContainer , textContainer , null ) ;
57+
58+ let span : Span = null ;
59+ // try to find the corresponding span using the spanRanges
60+ for ( let i = 0 ; i < owner . _spanRanges . length ; i ++ ) {
61+ let range = owner . _spanRanges [ i ] ;
62+ if ( ( range . location <= indexOfCharacter ) && ( range . location + range . length ) > indexOfCharacter ) {
63+ if ( owner . formattedText && owner . formattedText . spans . length > i ) {
64+ span = owner . formattedText . spans . getItem ( i ) ;
65+ }
66+ break ;
67+ }
68+ }
69+
70+ if ( span && span . tappable ) {
71+ // if the span is found and tappable emit the linkTap event
72+ span . _emit ( Span . linkTapEvent ) ;
73+ }
74+ }
75+ }
76+
77+ public static ObjCExposedMethods = {
78+ "linkTap" : { returns : interop . types . void , params : [ interop . types . id ] }
79+ } ;
80+ }
81+
1882export class TextBase extends TextBaseCommon {
1983
2084 public nativeViewProtected : UITextField | UITextView | UILabel | UIButton ;
2185 public nativeTextViewProtected : UITextField | UITextView | UILabel | UIButton ;
86+ private _tappable : boolean = false ;
87+ private _tapGestureRecognizer : UITapGestureRecognizer ;
88+ public _spanRanges : NSRange [ ] ;
89+
90+ public initNativeView ( ) : void {
91+ super . initNativeView ( ) ;
92+ this . _setTappableState ( false ) ;
93+ }
94+
95+ _setTappableState ( tappable : boolean ) {
96+ if ( this . _tappable !== tappable ) {
97+ this . _tappable = tappable ;
98+ if ( this . _tappable ) {
99+ const tapHandler = UILabelClickHandlerImpl . initWithOwner ( new WeakRef ( this ) ) ;
100+ // associate handler with menuItem or it will get collected by JSC.
101+ ( < any > this ) . handler = tapHandler ;
102+
103+ this . _tapGestureRecognizer = UITapGestureRecognizer . alloc ( ) . initWithTargetAction ( tapHandler , "linkTap" ) ;
104+ this . nativeViewProtected . userInteractionEnabled = true ;
105+ this . nativeViewProtected . addGestureRecognizer ( this . _tapGestureRecognizer ) ;
106+ }
107+ else {
108+ this . nativeViewProtected . userInteractionEnabled = false ;
109+ this . nativeViewProtected . removeGestureRecognizer ( this . _tapGestureRecognizer ) ;
110+ }
111+ }
112+ }
22113
23114 [ textProperty . getDefault ] ( ) : number | symbol {
24115 return resetSymbol ;
@@ -35,6 +126,7 @@ export class TextBase extends TextBaseCommon {
35126
36127 [ formattedTextProperty . setNative ] ( value : FormattedString ) {
37128 this . _setNativeText ( ) ;
129+ this . _setTappableState ( isStringTappable ( value ) ) ;
38130 textProperty . nativeValueChange ( this , ! value ? "" : value . toString ( ) ) ;
39131 this . _requestLayoutOnTextChanged ( ) ;
40132 }
@@ -253,6 +345,7 @@ export class TextBase extends TextBaseCommon {
253345
254346 createNSMutableAttributedString ( formattedString : FormattedString ) : NSMutableAttributedString {
255347 let mas = NSMutableAttributedString . alloc ( ) . init ( ) ;
348+ this . _spanRanges = [ ] ;
256349 if ( formattedString && formattedString . parent ) {
257350 for ( let i = 0 , spanStart = 0 , length = formattedString . spans . length ; i < length ; i ++ ) {
258351 const span = formattedString . spans . getItem ( i ) ;
@@ -265,6 +358,7 @@ export class TextBase extends TextBaseCommon {
265358
266359 const nsAttributedString = this . createMutableStringForSpan ( span , spanText ) ;
267360 mas . insertAttributedStringAtIndex ( nsAttributedString , spanStart ) ;
361+ this . _spanRanges . push ( { location : spanStart , length : spanText . length } ) ;
268362 spanStart += spanText . length ;
269363 }
270364 }
@@ -349,3 +443,17 @@ export function getTransformedText(text: string, textTransform: TextTransform):
349443function NSStringFromNSAttributedString ( source : NSAttributedString | string ) : NSString {
350444 return NSString . stringWithString ( source instanceof NSAttributedString && source . string || < string > source ) ;
351445}
446+
447+ function isStringTappable ( formattedString : FormattedString ) {
448+ if ( ! formattedString ) {
449+ return false ;
450+ }
451+ for ( let i = 0 , length = formattedString . spans . length ; i < length ; i ++ ) {
452+ const span = formattedString . spans . getItem ( i ) ;
453+ if ( span . tappable ) {
454+ return true ;
455+ }
456+ }
457+
458+ return false ;
459+ }
0 commit comments