¿Crear “enlaces” tap-able en el NSAttributedString de un UILabel?

He estado buscando esto por horas pero he fallado. Probablemente ni siquiera sé lo que debería estar buscando.

Muchas aplicaciones tienen texto y en este texto son hipervínculos web en rect. Cuando hago clic en ellos, se abre UIWebView . Lo que me desconcierta es que a menudo tienen enlaces personalizados, por ejemplo, si las palabras comienzan con # también se puede hacer clic y la aplicación responde abriendo otra vista. ¿Cómo puedo hacer eso? ¿Es posible con UILabel o necesito UITextView o algo más?

En general, si queremos tener un enlace clicable en el texto que muestra UILabel, tendríamos que resolver dos tareas independientes:

  1. Cambiar la apariencia de una parte del texto para que parezca un enlace
  2. Detectar y manejar toques en el enlace (abrir una URL es un caso particular)

El primero es facil. A partir de iOS 6, UILabel admite la visualización de cadenas atribuidas. Todo lo que necesita hacer es crear y configurar una instancia de NSMutableAttributedString:

 NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:@"String with a link" attributes:nil]; NSRange linkRange = NSMakeRange(14, 4); // for the word "link" in the string above NSDictionary *linkAttributes = @{ NSForegroundColorAttributeName : [UIColor colorWithRed:0.05 green:0.4 blue:0.65 alpha:1.0], NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle) }; [attributedString setAttributes:linkAttributes range:linkRange]; // Assign attributedText to UILabel label.attributedText = attributedString; 

¡Eso es! El código anterior hace que UILabel muestre String con un enlace

Ahora debemos detectar toques en este enlace. La idea es capturar todos los grifos dentro de UILabel y averiguar si la ubicación del grifo estaba lo suficientemente cerca del enlace. Para captar los toques, podemos agregar el reconocedor de toque de toque a la etiqueta. Asegúrese de habilitar userInteraction para la etiqueta, está desactivada de manera predeterminada:

 label.userInteractionEnabled = YES; [label addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapOnLabel:)]]; 

Ahora lo más sofisticado: averiguar si el tap estaba en donde se muestra el enlace y no en ninguna otra parte de la etiqueta. Si tuviéramos UILabel de línea única, esta tarea podría resolverse de manera relativamente fácil mediante la encoding de los límites de área donde se muestra el enlace, pero resuelvamos este problema de forma más elegante y para casos generales: UILabel de varias líneas sin conocimiento preliminar sobre el diseño del enlace.

Uno de los enfoques es usar las capacidades de la API de Kit de texto presentada en iOS 7:

 // Create instances of NSLayoutManager, NSTextContainer and NSTextStorage NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init]; NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeZero]; NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString]; // Configure layoutManager and textStorage [layoutManager addTextContainer:textContainer]; [textStorage addLayoutManager:layoutManager]; // Configure textContainer textContainer.lineFragmentPadding = 0.0; textContainer.lineBreakMode = label.lineBreakMode; textContainer.maximumNumberOfLines = label.numberOfLines; 

Guarde las instancias creadas y configuradas de NSLayoutManager, NSTextContainer y NSTextStorage en las propiedades de su clase (probablemente el descendiente de UIViewController); las necesitaremos en otros métodos.

Ahora, cada vez que la etiqueta cambie su marco, actualice el tamaño de TextContainer:

 - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; self.textContainer.size = self.label.bounds.size; } 

Y finalmente, detectar si el grifo estaba exactamente en el enlace:

 - (void)handleTapOnLabel:(UITapGestureRecognizer *)tapGesture { CGPoint locationOfTouchInLabel = [tapGesture locationInView:tapGesture.view]; CGSize labelSize = tapGesture.view.bounds.size; CGRect textBoundingBox = [self.layoutManager usedRectForTextContainer:self.textContainer]; CGPoint textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x, (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y); CGPoint locationOfTouchInTextContainer = CGPointMake(locationOfTouchInLabel.x - textContainerOffset.x, locationOfTouchInLabel.y - textContainerOffset.y); NSInteger indexOfCharacter = [self.layoutManager characterIndexForPoint:locationOfTouchInTextContainer inTextContainer:self.textContainer fractionOfDistanceBetweenInsertionPoints:nil]; NSRange linkRange = NSMakeRange(14, 4); // it's better to save the range somewhere when it was originally used for marking link in attributed string if (NSLocationInRange(indexOfCharacter, linkRange)) { // Open an URL, or handle the tap on the link in any other way [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"https://stackoverflow.com/"]]; } } 

Extiendo la solución detallada original de @NAlexN , con la excelente extensión UITapGestureRecognizer de UITapGestureRecognizer y la entrega en Swift .

Extendiendo UITapGestureRecognizer

 extension UITapGestureRecognizer { func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool { // Create instances of NSLayoutManager, NSTextContainer and NSTextStorage let layoutManager = NSLayoutManager() let textContainer = NSTextContainer(size: CGSize.zero) let textStorage = NSTextStorage(attributedString: label.attributedText!) // Configure layoutManager and textStorage layoutManager.addTextContainer(textContainer) textStorage.addLayoutManager(layoutManager) // Configure textContainer textContainer.lineFragmentPadding = 0.0 textContainer.lineBreakMode = label.lineBreakMode textContainer.maximumNumberOfLines = label.numberOfLines let labelSize = label.bounds.size textContainer.size = labelSize // Find the tapped character location and compare it to the specified range let locationOfTouchInLabel = self.locationInView(label) let textBoundingBox = layoutManager.usedRectForTextContainer(textContainer) let textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x, (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y); let locationOfTouchInTextContainer = CGPointMake(locationOfTouchInLabel.x - textContainerOffset.x, locationOfTouchInLabel.y - textContainerOffset.y); let indexOfCharacter = layoutManager.characterIndexForPoint(locationOfTouchInTextContainer, inTextContainer: textContainer, fractionOfDistanceBetweenInsertionPoints: nil) return NSLocationInRange(indexOfCharacter, targetRange) } } 

Uso

Configura UIGestureRecognizer para enviar acciones a tapLabel: y puedes detectar si se están myLabel los rangos objective en myLabel .

 @IBAction func tapLabel(gesture: UITapGestureRecognizer) { if gesture.didTapAttributedTextInLabel(myLabel, inRange: targetRange1) { print("Tapped targetRange1") } else if gesture.didTapAttributedTextInLabel(myLabel, inRange: targetRange2) { print("Tapped targetRange2") } else { print("Tapped none") } } 

IMPORTANTE: El UILabel de salto de línea de UILabel debe configurarse para ajustarse por palabra / char. De alguna manera, NSTextContainer supondrá que el texto es de una sola línea si el modo de salto de línea es diferente.

UIButtonTypeCustom es una etiqueta en la que se puede hacer clic si no configura imágenes para ella.

(Mi respuesta se basa en la excelente respuesta de @ NAlexN. No voy a duplicar su explicación detallada de cada paso aquí).

Me pareció más conveniente y sencillo agregar compatibilidad con el texto UILabel con capacidad de pulsación como una categoría para UITapGestureRecognizer. (No es necesario usar los detectores de datos de UITextView, como sugieren algunas respuestas).

Agregue el siguiente método a su categoría UITapGestureRecognizer:

 /** Returns YES if the tap gesture was within the specified range of the attributed text of the label. */ - (BOOL)didTapAttributedTextInLabel:(UILabel *)label inRange:(NSRange)targetRange { NSParameterAssert(label != nil); CGSize labelSize = label.bounds.size; // create instances of NSLayoutManager, NSTextContainer and NSTextStorage NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init]; NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeZero]; NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:label.attributedText]; // configure layoutManager and textStorage [layoutManager addTextContainer:textContainer]; [textStorage addLayoutManager:layoutManager]; // configure textContainer for the label textContainer.lineFragmentPadding = 0.0; textContainer.lineBreakMode = label.lineBreakMode; textContainer.maximumNumberOfLines = label.numberOfLines; textContainer.size = labelSize; // find the tapped character location and compare it to the specified range CGPoint locationOfTouchInLabel = [self locationInView:label]; CGRect textBoundingBox = [layoutManager usedRectForTextContainer:textContainer]; CGPoint textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x, (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y); CGPoint locationOfTouchInTextContainer = CGPointMake(locationOfTouchInLabel.x - textContainerOffset.x, locationOfTouchInLabel.y - textContainerOffset.y); NSInteger indexOfCharacter = [layoutManager characterIndexForPoint:locationOfTouchInTextContainer inTextContainer:textContainer fractionOfDistanceBetweenInsertionPoints:nil]; if (NSLocationInRange(indexOfCharacter, targetRange)) { return YES; } else { return NO; } } 

Código de ejemplo

 // (in your view controller) // create your label, gesture recognizer, attributed text, and get the range of the "link" in your label myLabel.userInteractionEnabled = YES; [myLabel addGestureRecognizer: [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapOnLabel:)]]; // create your attributed text and keep an ivar of your "link" text range NSAttributedString *plainText; NSAttributedString *linkText; plainText = [[NSMutableAttributedString alloc] initWithString:@"Add label links with UITapGestureRecognizer" attributes:nil]; linkText = [[NSMutableAttributedString alloc] initWithString:@" Learn more..." attributes:@{ NSForegroundColorAttributeName:[UIColor blueColor] }]; NSMutableAttributedString *attrText = [[NSMutableAttributedString alloc] init]; [attrText appendAttributedString:plainText]; [attrText appendAttributedString:linkText]; // ivar -- keep track of the target range so you can compare in the callback targetRange = NSMakeRange(plainText.length, linkText.length); 

Retrollamada de gestos

 // handle the gesture recognizer callback and call the category method - (void)handleTapOnLabel:(UITapGestureRecognizer *)tapGesture { BOOL didTapLink = [tapGesture didTapAttributedTextInLabel:myLabel inRange:targetRange]; NSLog(@"didTapLink: %d", didTapLink); } 

Una vieja pregunta, pero si alguien puede usar un UITextView lugar de un UILabel , entonces es fácil. Las URL estándar, los números de teléfono, etc. se detectarán automáticamente (y se podrá hacer clic en ellos).

Sin embargo, si necesita una detección personalizada, es decir, si desea poder llamar a cualquier método personalizado después de que un usuario haga clic en una palabra en particular, debe usar NSAttributedStrings con un atributo NSLinkAttributeName que apuntará a un esquema URL personalizado (como se opone a tener el esquema http url por defecto). Ray Wenderlich lo tiene cubierto aquí

Citando el código del enlace mencionado anteriormente:

 NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:@"This is an example by @marcelofabri_"]; [attributedString addAttribute:NSLinkAttributeName value:@"username://marcelofabri_" range:[[attributedString string] rangeOfString:@"@marcelofabri_"]]; NSDictionary *linkAttributes = @{NSForegroundColorAttributeName: [UIColor greenColor], NSUnderlineColorAttributeName: [UIColor lightGrayColor], NSUnderlineStyleAttributeName: @(NSUnderlinePatternSolid)}; // assume that textView is a UITextView previously created (either by code or Interface Builder) textView.linkTextAttributes = linkAttributes; // customizes the appearance of links textView.attributedText = attributedString; textView.delegate = self; 

Para detectar esos clics de enlace, implemente esto:

 - (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange { if ([[URL scheme] isEqualToString:@"username"]) { NSString *username = [URL host]; // do something with this username // ... return NO; } return YES; // let the system open this URL } 

PD: asegúrese de que su UITextView sea selectable .

UITextView admite detectores de datos en OS3.0, mientras que UILabel no.

Si habilita los detectores de datos en el UITextView y su texto contiene direcciones URL, números de teléfono, etc., aparecerán como enlaces.

Como mencioné en esta publicación , aquí hay una biblioteca liviana que he creado especialmente para enlaces en UILabel FRHyperLabel .

Para lograr un efecto como este:

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque quis blandit eros, sit amet vehicula justo. Nam en urna neque. Maecenas ac semie sem porta dictum nec vel tellus.

usar código:

 //Step 1: Define a normal attributed string for non-link texts NSString *string = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque quis blandit eros, sit amet vehicula justo. Nam at urna neque. Maecenas ac sem eu sem porta dictum nec vel tellus."; NSDictionary *attributes = @{NSFontAttributeName: [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]}; label.attributedText = [[NSAttributedString alloc]initWithString:string attributes:attributes]; //Step 2: Define a selection handler block void(^handler)(FRHyperLabel *label, NSString *substring) = ^(FRHyperLabel *label, NSString *substring){ NSLog(@"Selected: %@", substring); }; //Step 3: Add link substrings [label setLinksForSubstrings:@[@"Lorem", @"Pellentesque", @"blandit", @"Maecenas"] withLinkHandler:handler]; 

Creé la subclase UILabel llamada ResponsiveLabel que se basa en la API textkit presentada en iOS 7. Utiliza el mismo enfoque sugerido por NAlexN . Proporciona flexibilidad para especificar un patrón para buscar en el texto. Se pueden especificar los estilos que se aplicarán a esos patrones, así como la acción que se realizará al tocar los patrones.

 //Detects email in text NSString *emailRegexString = @"[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[AZ]{2,4}"; NSError *error; NSRegularExpression *regex = [[NSRegularExpression alloc]initWithPattern:emailRegexString options:0 error:&error]; PatternDescriptor *descriptor = [[PatternDescriptor alloc]initWithRegex:regex withSearchType:PatternSearchTypeAll withPatternAttributes:@{NSForegroundColorAttributeName:[UIColor redColor]}]; [self.customLabel enablePatternDetection:descriptor]; 

Si desea hacer que una secuencia se pueda hacer clic, puede hacerlo de esta manera. Este código aplica atributos a cada aparición de la cadena “texto”.

 PatternTapResponder tapResponder = ^(NSString *string) { NSLog(@"tapped = %@",string); }; [self.customLabel enableStringDetection:@"text" withAttributes:@{NSForegroundColorAttributeName:[UIColor redColor], RLTapResponderAttributeName: tapResponder}]; 

Traducir la extensión de @ samwize a Swift 4:

 extension UITapGestureRecognizer { func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool { guard let attrString = label.attributedText else { return false } let layoutManager = NSLayoutManager() let textContainer = NSTextContainer(size: .zero) let textStorage = NSTextStorage(attributedString: attrString) layoutManager.addTextContainer(textContainer) textStorage.addLayoutManager(layoutManager) textContainer.lineFragmentPadding = 0 textContainer.lineBreakMode = label.lineBreakMode textContainer.maximumNumberOfLines = label.numberOfLines let labelSize = label.bounds.size textContainer.size = labelSize let locationOfTouchInLabel = self.location(in: label) let textBoundingBox = layoutManager.usedRect(for: textContainer) let textContainerOffset = CGPoint(x: (labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x, y: (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y) let locationOfTouchInTextContainer = CGPoint(x: locationOfTouchInLabel.x - textContainerOffset.x, y: locationOfTouchInLabel.y - textContainerOffset.y) let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil) return NSLocationInRange(indexOfCharacter, targetRange) } } 

Para configurar el reconocedor (una vez que coloreó el texto y esas cosas):

 lblTermsOfUse.isUserInteractionEnabled = true lblTermsOfUse.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTapOnLabel(_:)))) 

… luego el reconocedor de gestos:

 @objc func handleTapOnLabel(_ recognizer: UITapGestureRecognizer) { guard let text = lblAgreeToTerms.attributedText?.string else { return } if let range = text.range(of: NSLocalizedString("_onboarding_terms", comment: "terms")), recognizer.didTapAttributedTextInLabel(label: lblAgreeToTerms, inRange: NSRange(range, in: text)) { goToTermsAndConditions() } else if let range = text.range(of: NSLocalizedString("_onboarding_privacy", comment: "privacy")), recognizer.didTapAttributedTextInLabel(label: lblAgreeToTerms, inRange: NSRange(range, in: text)) { goToPrivacyPolicy() } } 

Aquí hay un código de ejemplo para hipervincular a UILabel: Fuente: http://sickprogrammersarea.blogspot.in/2014/03/adding-links-to-uilabel.html

 #import "ViewController.h" #import "TTTAttributedLabel.h" @interface ViewController () @end @implementation ViewController { UITextField *loc; TTTAttributedLabel *data; } - (void)viewDidLoad { [super viewDidLoad]; UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(5, 20, 80, 25) ]; [lbl setText:@"Text:"]; [lbl setFont:[UIFont fontWithName:@"Verdana" size:16]]; [lbl setTextColor:[UIColor grayColor]]; loc=[[UITextField alloc] initWithFrame:CGRectMake(4, 20, 300, 30)]; //loc.backgroundColor = [UIColor grayColor]; loc.borderStyle=UITextBorderStyleRoundedRect; loc.clearButtonMode=UITextFieldViewModeWhileEditing; //[loc setText:@"Enter Location"]; loc.clearsOnInsertion = YES; loc.leftView=lbl; loc.leftViewMode=UITextFieldViewModeAlways; [loc setDelegate:self]; [self.view addSubview:loc]; [loc setRightViewMode:UITextFieldViewModeAlways]; CGRect frameimg = CGRectMake(110, 70, 70,30); UIButton *srchButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; srchButton.frame=frameimg; [srchButton setTitle:@"Go" forState:UIControlStateNormal]; [srchButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; srchButton.backgroundColor=[UIColor clearColor]; [srchButton addTarget:self action:@selector(go:) forControlEvents:UIControlEventTouchDown]; [self.view addSubview:srchButton]; data = [[TTTAttributedLabel alloc] initWithFrame:CGRectMake(5, 120,self.view.frame.size.width,200) ]; [data setFont:[UIFont fontWithName:@"Verdana" size:16]]; [data setTextColor:[UIColor blackColor]]; data.numberOfLines=0; data.delegate = self; data.enabledTextCheckingTypes=NSTextCheckingTypeLink|NSTextCheckingTypePhoneNumber; [self.view addSubview:data]; } - (void)attributedLabel:(TTTAttributedLabel *)label didSelectLinkWithURL:(NSURL *)url { NSString *val=[[NSString alloc]initWithFormat:@"%@",url]; if ([[url scheme] hasPrefix:@"mailto"]) { NSLog(@" mail URL Selected : %@",url); MFMailComposeViewController *comp=[[MFMailComposeViewController alloc]init]; [comp setMailComposeDelegate:self]; if([MFMailComposeViewController canSendMail]) { NSString *recp=[[val substringToIndex:[val length]] substringFromIndex:7]; NSLog(@"Recept : %@",recp); [comp setToRecipients:[NSArray arrayWithObjects:recp, nil]]; [comp setSubject:@"From my app"]; [comp setMessageBody:@"Hello bro" isHTML:NO]; [comp setModalTransitionStyle:UIModalTransitionStyleCrossDissolve]; [self presentViewController:comp animated:YES completion:nil]; } } else{ [[UIApplication sharedApplication] openURL:[NSURL URLWithString:val]]; } } -(void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error{ if(error) { UIAlertView *alrt=[[UIAlertView alloc]initWithTitle:@"Erorr" message:@"Some error occureed" delegate:nil cancelButtonTitle:@"" otherButtonTitles:nil, nil]; [alrt show]; [self dismissViewControllerAnimated:YES completion:nil]; } else{ [self dismissViewControllerAnimated:YES completion:nil]; } } - (void)attributedLabel:(TTTAttributedLabel *)label didSelectLinkWithPhoneNumber:(NSString *)phoneNumber { NSLog(@"Phone Number Selected : %@",phoneNumber); UIDevice *device = [UIDevice currentDevice]; if ([[device model] isEqualToString:@"iPhone"] ) { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"tel:%@",phoneNumber]]]; } else { UIAlertView *Notpermitted=[[UIAlertView alloc] initWithTitle:@"Alert" message:@"Your device doesn't support this feature." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [Notpermitted show]; } } -(void)go:(id)sender { [data setText:loc.text]; } -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { NSLog(@"Reached"); [loc resignFirstResponder]; } 

Trabajó en Swift 3, pegando todo el código aquí

  //****Make sure the textview 'Selectable' = checked, and 'Editable = Unchecked' import UIKit class ViewController: UIViewController, UITextViewDelegate { @IBOutlet var theNewTextView: UITextView! override func viewDidLoad() { super.viewDidLoad() //****textview = Selectable = checked, and Editable = Unchecked theNewTextView.delegate = self let theString = NSMutableAttributedString(string: "Agree to Terms") let theRange = theString.mutableString.range(of: "Terms") theString.addAttribute(NSLinkAttributeName, value: "ContactUs://", range: theRange) let theAttribute = [NSForegroundColorAttributeName: UIColor.blue, NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue] as [String : Any] theNewTextView.linkTextAttributes = theAttribute theNewTextView.attributedText = theString theString.setAttributes(theAttribute, range: theRange) } func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { if (URL.scheme?.hasPrefix("ContactUs://"))! { return false //interaction not allowed } //*** Set storyboard id same as VC name self.navigationController!.pushViewController((self.storyboard?.instantiateViewController(withIdentifier: "TheLastViewController"))! as UIViewController, animated: true) return true } } 

Aquí hay una versión rápida de la respuesta de NAlexN.

 class TapabbleLabel: UILabel { let layoutManager = NSLayoutManager() let textContainer = NSTextContainer(size: CGSize.zero) var textStorage = NSTextStorage() { didSet { textStorage.addLayoutManager(layoutManager) } } var onCharacterTapped: ((label: UILabel, characterIndex: Int) -> Void)? let tapGesture = UITapGestureRecognizer() override var attributedText: NSAttributedString? { didSet { if let attributedText = attributedText { textStorage = NSTextStorage(attributedString: attributedText) } else { textStorage = NSTextStorage() } } } override var lineBreakMode: NSLineBreakMode { didSet { textContainer.lineBreakMode = lineBreakMode } } override var numberOfLines: Int { didSet { textContainer.maximumNumberOfLines = numberOfLines } } /** Creates a new view with the passed coder. :param: aDecoder The a decoder :returns: the created new view. */ required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setUp() } /** Creates a new view with the passed frame. :param: frame The frame :returns: the created new view. */ override init(frame: CGRect) { super.init(frame: frame) setUp() } /** Sets up the view. */ func setUp() { userInteractionEnabled = true layoutManager.addTextContainer(textContainer) textContainer.lineFragmentPadding = 0 textContainer.lineBreakMode = lineBreakMode textContainer.maximumNumberOfLines = numberOfLines tapGesture.addTarget(self, action: #selector(TapabbleLabel.labelTapped(_:))) addGestureRecognizer(tapGesture) } override func layoutSubviews() { super.layoutSubviews() textContainer.size = bounds.size } func labelTapped(gesture: UITapGestureRecognizer) { guard gesture.state == .Ended else { return } let locationOfTouch = gesture.locationInView(gesture.view) let textBoundingBox = layoutManager.usedRectForTextContainer(textContainer) let textContainerOffset = CGPoint(x: (bounds.width - textBoundingBox.width) / 2 - textBoundingBox.minX, y: (bounds.height - textBoundingBox.height) / 2 - textBoundingBox.minY) let locationOfTouchInTextContainer = CGPoint(x: locationOfTouch.x - textContainerOffset.x, y: locationOfTouch.y - textContainerOffset.y) let indexOfCharacter = layoutManager.characterIndexForPoint(locationOfTouchInTextContainer, inTextContainer: textContainer, fractionOfDistanceBetweenInsertionPoints: nil) onCharacterTapped?(label: self, characterIndex: indexOfCharacter) } 

}

A continuación, puede crear una instancia de esa clase dentro de su método viewDidLoad esta manera:

 let label = TapabbleLabel() label.translatesAutoresizingMaskIntoConstraints = false view.addSubview(label) view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[view]-|", options: [], metrics: nil, views: ["view" : label])) view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[view]-|", options: [], metrics: nil, views: ["view" : label])) let attributedString = NSMutableAttributedString(string: "String with a link", attributes: nil) let linkRange = NSMakeRange(14, 4); // for the word "link" in the string above let linkAttributes: [String : AnyObject] = [ NSForegroundColorAttributeName : UIColor.blueColor(), NSUnderlineStyleAttributeName : NSUnderlineStyle.StyleSingle.rawValue, NSLinkAttributeName: "http://www.apple.com"] attributedString.setAttributes(linkAttributes, range:linkRange) label.attributedText = attributedString label.onCharacterTapped = { label, characterIndex in if let attribute = label.attributedText?.attribute(NSLinkAttributeName, atIndex: characterIndex, effectiveRange: nil) as? String, let url = NSURL(string: attribute) { UIApplication.sharedApplication().openURL(url) } } 

Es mejor tener un atributo personalizado para usar cuando se toca un carácter. Now, it’s the NSLinkAttributeName , but could be anything and you can use that value to do other things other than opening a url, you can do any custom action.

I’m extending @samwize’s answer to handle multi-line UILabel and give an example on using for a UIButton

 extension UITapGestureRecognizer { func didTapAttributedTextInButton(button: UIButton, inRange targetRange: NSRange) -> Bool { guard let label = button.titleLabel else { return false } return didTapAttributedTextInLabel(label, inRange: targetRange) } func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool { // Create instances of NSLayoutManager, NSTextContainer and NSTextStorage let layoutManager = NSLayoutManager() let textContainer = NSTextContainer(size: CGSize.zero) let textStorage = NSTextStorage(attributedString: label.attributedText!) // Configure layoutManager and textStorage layoutManager.addTextContainer(textContainer) textStorage.addLayoutManager(layoutManager) // Configure textContainer textContainer.lineFragmentPadding = 0.0 textContainer.lineBreakMode = label.lineBreakMode textContainer.maximumNumberOfLines = label.numberOfLines let labelSize = label.bounds.size textContainer.size = labelSize // Find the tapped character location and compare it to the specified range let locationOfTouchInLabel = self.locationInView(label) let textBoundingBox = layoutManager.usedRectForTextContainer(textContainer) let textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x, (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y); let locationOfTouchInTextContainer = CGPointMake((locationOfTouchInLabel.x - textContainerOffset.x), 0 ); // Adjust for multiple lines of text let lineModifier = Int(ceil(locationOfTouchInLabel.y / label.font.lineHeight)) - 1 let rightMostFirstLinePoint = CGPointMake(labelSize.width, 0) let charsPerLine = layoutManager.characterIndexForPoint(rightMostFirstLinePoint, inTextContainer: textContainer, fractionOfDistanceBetweenInsertionPoints: nil) let indexOfCharacter = layoutManager.characterIndexForPoint(locationOfTouchInTextContainer, inTextContainer: textContainer, fractionOfDistanceBetweenInsertionPoints: nil) let adjustedRange = indexOfCharacter + (lineModifier * charsPerLine) return NSLocationInRange(adjustedRange, targetRange) } } 

Like there is reported in earlier awnser the UITextView is able to handle touches on links. This can easily be extended by making other parts of the text work as links. The AttributedTextView library is a UITextView subclass that makes it very easy to handle these. For more info see: https://github.com/evermeer/AttributedTextView

You can make any part of the text interact like this (where textView1 is a UITextView IBoutlet):

 textView1.attributer = "1. ".red .append("This is the first test. ").green .append("Click on ").black .append("evict.nl").makeInteract { _ in UIApplication.shared.open(URL(string: "http://evict.nl")!, options: [:], completionHandler: { completed in }) }.underline .append(" for testing links. ").black .append("Next test").underline.makeInteract { _ in print("NEXT") } .all.font(UIFont(name: "SourceSansPro-Regular", size: 16)) .setLinkColor(UIColor.purple) 

And for handling hashtags and mentions you can use code like this:

 textView1.attributer = "@test: What #hashtags do we have in @evermeer #AtributedTextView library" .matchHashtags.underline .matchMentions .makeInteract { link in UIApplication.shared.open(URL(string: "https://twitter.com\(link.replacingOccurrences(of: "@", with: ""))")!, options: [:], completionHandler: { completed in }) } 

For fully custom links, you’ll need to use a UIWebView – you can intercept the calls out, so that you can go to some other part of your app instead when a link is pressed.

I had a hard time dealing with this… UILabel with links on it on attributed text… it is just a headache so I ended up using ZSWTappableLabel .

Create the class with the following .h and .m files. In the .m file there is the following function

  - (void)linkAtPoint:(CGPoint)location 

Inside this function we will check the ranges of substrings for which we need to give actions. Use your own logic to put your ranges.

And following is the usage of the subclass

 TaggedLabel *label = [[TaggedLabel alloc] initWithFrame:CGRectMake(100, 100, 100, 100)]; [self.view addSubview:label]; label.numberOfLines = 0; NSMutableAttributedString *attributtedString = [[NSMutableAttributedString alloc] initWithString : @"My name is @jjpp" attributes : @{ NSFontAttributeName : [UIFont systemFontOfSize:10],}]; //Do not forget to add the font attribute.. else it wont work.. it is very important [attributtedString addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(11, 5)];//you can give this range inside the .m function mentioned above 

following is the .h file

 #import  @interface TaggedLabel : UILabel @property(nonatomic, strong)NSLayoutManager *layoutManager; @property(nonatomic, strong)NSTextContainer *textContainer; @property(nonatomic, strong)NSTextStorage *textStorage; @property(nonatomic, strong)NSArray *tagsArray; @property(readwrite, copy) tagTapped nameTagTapped; @end 

following is the .m file

 #import "TaggedLabel.h" @implementation TaggedLabel - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.userInteractionEnabled = YES; } return self; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { self.userInteractionEnabled = YES; } return self; } - (void)setupTextSystem { _layoutManager = [[NSLayoutManager alloc] init]; _textContainer = [[NSTextContainer alloc] initWithSize:CGSizeZero]; _textStorage = [[NSTextStorage alloc] initWithAttributedString:self.attributedText]; // Configure layoutManager and textStorage [_layoutManager addTextContainer:_textContainer]; [_textStorage addLayoutManager:_layoutManager]; // Configure textContainer _textContainer.lineFragmentPadding = 0.0; _textContainer.lineBreakMode = NSLineBreakByWordWrapping; _textContainer.maximumNumberOfLines = 0; self.userInteractionEnabled = YES; self.textContainer.size = self.bounds.size; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { if (!_layoutManager) { [self setupTextSystem]; } // Get the info for the touched link if there is one CGPoint touchLocation = [[touches anyObject] locationInView:self]; [self linkAtPoint:touchLocation]; } - (void)linkAtPoint:(CGPoint)location { // Do nothing if we have no text if (_textStorage.string.length == 0) { return; } // Work out the offset of the text in the view CGPoint textOffset = [self calcGlyphsPositionInView]; // Get the touch location and use text offset to convert to text cotainer coords location.x -= textOffset.x; location.y -= textOffset.y; NSUInteger touchedChar = [_layoutManager glyphIndexForPoint:location inTextContainer:_textContainer]; // If the touch is in white space after the last glyph on the line we don't // count it as a hit on the text NSRange lineRange; CGRect lineRect = [_layoutManager lineFragmentUsedRectForGlyphAtIndex:touchedChar effectiveRange:&lineRange]; if (CGRectContainsPoint(lineRect, location) == NO) { return; } // Find the word that was touched and call the detection block NSRange range = NSMakeRange(11, 5);//for this example i'm hardcoding the range here. In a real scenario it should be iterated through an array for checking all the ranges if ((touchedChar >= range.location) && touchedChar < (range.location + range.length)) { NSLog(@"range-->>%@",self.tagsArray[i][@"range"]); } } - (CGPoint)calcGlyphsPositionInView { CGPoint textOffset = CGPointZero; CGRect textBounds = [_layoutManager usedRectForTextContainer:_textContainer]; textBounds.size.width = ceil(textBounds.size.width); textBounds.size.height = ceil(textBounds.size.height); if (textBounds.size.height < self.bounds.size.height) { CGFloat paddingHeight = (self.bounds.size.height - textBounds.size.height) / 2.0; textOffset.y = paddingHeight; } if (textBounds.size.width < self.bounds.size.width) { CGFloat paddingHeight = (self.bounds.size.width - textBounds.size.width) / 2.0; textOffset.x = paddingHeight; } return textOffset; } @end 

I’d strongly recommend using a library that automatically detects URLs in text and converts them to links. Tratar:

  • TTTAttributedLabel ( pod )
  • ZSWTappableLabel ( pod ).

Both are under MIT license.

based on Charles Gamble answer, this what I used (I removed some lines that confused me and gave me wrong indexed) :

 - (BOOL)didTapAttributedTextInLabel:(UILabel *)label inRange:(NSRange)targetRange TapGesture:(UIGestureRecognizer*) gesture{ NSParameterAssert(label != nil); // create instances of NSLayoutManager, NSTextContainer and NSTextStorage NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init]; NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:label.attributedText]; // configure layoutManager and textStorage [textStorage addLayoutManager:layoutManager]; // configure textContainer for the label NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeMake(label.frame.size.width, label.frame.size.height)]; textContainer.lineFragmentPadding = 0.0; textContainer.lineBreakMode = label.lineBreakMode; textContainer.maximumNumberOfLines = label.numberOfLines; // find the tapped character location and compare it to the specified range CGPoint locationOfTouchInLabel = [gesture locationInView:label]; [layoutManager addTextContainer:textContainer]; //(move here, not sure it that matter that calling this line after textContainer is set NSInteger indexOfCharacter = [layoutManager characterIndexForPoint:locationOfTouchInLabel inTextContainer:textContainer fractionOfDistanceBetweenInsertionPoints:nil]; if (NSLocationInRange(indexOfCharacter, targetRange)) { return YES; } else { return NO; } } 

Here’s a drop-in Objective-C category that enables clickable links in existing UILabel.attributedText strings, exploiting the existing NSLinkAttributeName attribute.

 @interface UILabel (GSBClickableLinks)  @property BOOL enableLinks; @end #import  static const void *INDEX; static const void *TAP; @implementation UILabel (GSBClickableLinks) - (void)setEnableLinks:(BOOL)enableLinks { UITapGestureRecognizer *tap = objc_getAssociatedObject(self, &TAP); // retreive tap if (enableLinks && !tap) { // add a gestureRegonzier to the UILabel to detect taps tap = [UITapGestureRecognizer.alloc initWithTarget:self action:@selector(openLink)]; tap.delegate = self; [self addGestureRecognizer:tap]; objc_setAssociatedObject(self, &TAP, tap, OBJC_ASSOCIATION_RETAIN_NONATOMIC); // save tap } self.userInteractionEnabled = enableLinks; // note - when false UILAbel wont receive taps, hence disable links } - (BOOL)enableLinks { return (BOOL)objc_getAssociatedObject(self, &TAP); // ie tap != nil } // First check whether user tapped on a link within the attributedText of the label. // If so, then the our label's gestureRecogizer will subsequently fire, and open the corresponding NSLinkAttributeName. // If not, then the tap will get passed along, eg to the enclosing UITableViewCell... // Note: save which character in the attributedText was clicked so that we dont have to redo everything again in openLink. - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { if (gestureRecognizer != objc_getAssociatedObject(self, &TAP)) return YES; // dont block other gestures (eg swipe) // Re-layout the attributedText to find out what was tapped NSTextContainer *textContainer = [NSTextContainer.alloc initWithSize:self.frame.size]; textContainer.lineFragmentPadding = 0; textContainer.maximumNumberOfLines = self.numberOfLines; textContainer.lineBreakMode = self.lineBreakMode; NSLayoutManager *layoutManager = NSLayoutManager.new; [layoutManager addTextContainer:textContainer]; NSTextStorage *textStorage = [NSTextStorage.alloc initWithAttributedString:self.attributedText]; [textStorage addLayoutManager:layoutManager]; NSUInteger index = [layoutManager characterIndexForPoint:[gestureRecognizer locationInView:self] inTextContainer:textContainer fractionOfDistanceBetweenInsertionPoints:NULL]; objc_setAssociatedObject(self, &INDEX, @(index), OBJC_ASSOCIATION_RETAIN_NONATOMIC); // save index return (BOOL)[self.attributedText attribute:NSLinkAttributeName atIndex:index effectiveRange:NULL]; // tapped on part of a link? } - (void)openLink { NSUInteger index = [objc_getAssociatedObject(self, &INDEX) unsignedIntegerValue]; // retrieve index NSURL *url = [self.attributedText attribute:NSLinkAttributeName atIndex:index effectiveRange:NULL]; if (url && [UIApplication.sharedApplication canOpenURL:url]) [UIApplication.sharedApplication openURL:url]; } @end 

This would be a bit cleaner done via a UILabel subclass (ie none of the objc_getAssociatedObject mess), but if you are like me you prefer to avoid having to make unnecessary (3rd party) subclasses just to add some extra function to existing UIKit classes. Also, this has the beauty that it adds clickable-links to any existing UILabel, eg existing UITableViewCells !

I’ve tried to make it as minimally invasive as possible by using the existing NSLinkAttributeName attribute stuff already available in NSAttributedString. So its a simple as:

 NSURL *myURL = [NSURL URLWithString:@"http://www.google.com"]; NSMutableAttributedString *myString = [NSMutableAttributedString.alloc initWithString:@"This string has a clickable link: "]; [myString appendAttributedString:[NSAttributedString.alloc initWithString:@"click here" attributes:@{NSLinkAttributeName:myURL}]]; ... myLabel.attributedText = myString; myLabel.enableLinks = YES; // yes, that's all! :-) 

Basically, it works by adding a UIGestureRecognizer to your UILabel. The hard work is done in gestureRecognizerShouldBegin: , which re-layouts the attributedText string to find out which character was tapped on. If this character was part of a NSLinkAttributeName then the gestureRecognizer will subsequently fire, retrieve the corresponding URL (from the NSLinkAttributeName value), and open the link per the usual [UIApplication.sharedApplication openURL:url] process.

Note – by doing all this in gestureRecognizerShouldBegin: , if you dont happen to tap on a link in the label, the event is passed along. So, for example, your UITableViewCell will capture taps on links, but otherwise behave normally (select cell, unselect, scroll, …).

I’ve put this in a GitHub repository here . Adapted from Kai Burghardt’s SO posting here .

Here’s a Swift implementation that is about as minimal as possible that also includes touch feedback. Advertencias:

  1. You must set fonts in your NSAttributedStrings
  2. You can only use NSAttributedStrings!
  3. You must ensure your links cannot wrap (use non breaking spaces: "\u{a0}" )
  4. You cannot change the lineBreakMode or numberOfLines after setting the text
  5. You create links by adding attributes with .link keys

.

 public class LinkLabel: UILabel { private var storage: NSTextStorage? private let textContainer = NSTextContainer() private let layoutManager = NSLayoutManager() private var selectedBackgroundView = UIView() override init(frame: CGRect) { super.init(frame: frame) textContainer.lineFragmentPadding = 0 layoutManager.addTextContainer(textContainer) textContainer.layoutManager = layoutManager isUserInteractionEnabled = true selectedBackgroundView.isHidden = true selectedBackgroundView.backgroundColor = UIColor(white: 0, alpha: 0.3333) selectedBackgroundView.layer.cornerRadius = 4 addSubview(selectedBackgroundView) } public required convenience init(coder: NSCoder) { self.init(frame: .zero) } public override func layoutSubviews() { super.layoutSubviews() textContainer.size = frame.size } public override func touchesBegan(_ touches: Set, with event: UIEvent?) { super.touchesBegan(touches, with: event) setLink(for: touches) } public override func touchesMoved(_ touches: Set, with event: UIEvent?) { super.touchesMoved(touches, with: event) setLink(for: touches) } private func setLink(for touches: Set) { if let pt = touches.first?.location(in: self), let (characterRange, _) = link(at: pt) { let glyphRange = layoutManager.glyphRange(forCharacterRange: characterRange, actualCharacterRange: nil) selectedBackgroundView.frame = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer).insetBy(dx: -3, dy: -3) selectedBackgroundView.isHidden = false } else { selectedBackgroundView.isHidden = true } } public override func touchesCancelled(_ touches: Set, with event: UIEvent?) { super.touchesCancelled(touches, with: event) selectedBackgroundView.isHidden = true } public override func touchesEnded(_ touches: Set, with event: UIEvent?) { super.touchesEnded(touches, with: event) selectedBackgroundView.isHidden = true if let pt = touches.first?.location(in: self), let (_, url) = link(at: pt) { UIApplication.shared.open(url) } } private func link(at point: CGPoint) -> (NSRange, URL)? { let touchedGlyph = layoutManager.glyphIndex(for: point, in: textContainer) let touchedChar = layoutManager.characterIndexForGlyph(at: touchedGlyph) var range = NSRange() let attrs = attributedText!.attributes(at: touchedChar, effectiveRange: &range) if let urlstr = attrs[.link] as? String { return (range, URL(string: urlstr)!) } else { return nil } } public override var attributedText: NSAttributedString? { didSet { textContainer.maximumNumberOfLines = numberOfLines textContainer.lineBreakMode = lineBreakMode if let txt = attributedText { storage = NSTextStorage(attributedString: txt) storage!.addLayoutManager(layoutManager) layoutManager.textStorage = storage textContainer.size = frame.size } } } } 

TAGS #Swift2.0

I take inspiration on – excellent – @NAlexN’s answer and I decide to write by myself a wrapper of UILabel.
I also tried TTTAttributedLabel but I can’t make it works.

Hope you can appreciate this code, any suggestions are welcome!

 import Foundation @objc protocol TappableLabelDelegate { optional func tappableLabel(tabbableLabel: TappableLabel, didTapUrl: NSURL, atRange: NSRange) } /// Represent a label with attributed text inside. /// We can add a correspondence between a range of the attributed string an a link (URL) /// By default, link will be open on the external browser @see 'openLinkOnExternalBrowser' class TappableLabel: UILabel { // MARK: - Public properties - var links: NSMutableDictionary = [:] var openLinkOnExternalBrowser = true var delegate: TappableLabelDelegate? // MARK: - Constructors - override func awakeFromNib() { super.awakeFromNib() self.enableInteraction() } override init(frame: CGRect) { super.init(frame: frame) self.enableInteraction() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } private func enableInteraction() { self.userInteractionEnabled = true self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: Selector("didTapOnLabel:"))) } // MARK: - Public methods - /** Add correspondence between a range and a link. - parameter url: url. - parameter range: range on which couple url. */ func addLink(url url: String, atRange range: NSRange) { self.links[url] = range } // MARK: - Public properties - /** Action rised on user interaction on label. - parameter tapGesture: gesture. */ func didTapOnLabel(tapGesture: UITapGestureRecognizer) { let labelSize = self.bounds.size; let layoutManager = NSLayoutManager() let textContainer = NSTextContainer(size: CGSizeZero) let textStorage = NSTextStorage(attributedString: self.attributedText!) // configure textContainer for the label textContainer.lineFragmentPadding = 0 textContainer.lineBreakMode = self.lineBreakMode textContainer.maximumNumberOfLines = self.numberOfLines textContainer.size = labelSize; // configure layoutManager and textStorage layoutManager.addTextContainer(textContainer) textStorage.addLayoutManager(layoutManager) // find the tapped character location and compare it to the specified range let locationOfTouchInLabel = tapGesture.locationInView(self) let textBoundingBox = layoutManager.usedRectForTextContainer(textContainer) let textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x, (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y) let locationOfTouchInTextContainer = CGPointMake(locationOfTouchInLabel.x - textContainerOffset.x, locationOfTouchInLabel.y - textContainerOffset.y) let indexOfCharacter = layoutManager.characterIndexForPoint(locationOfTouchInTextContainer, inTextContainer:textContainer, fractionOfDistanceBetweenInsertionPoints: nil) for (url, value) in self.links { if let range = value as? NSRange { if NSLocationInRange(indexOfCharacter, range) { let url = NSURL(string: url as! String)! if self.openLinkOnExternalBrowser { UIApplication.sharedApplication().openURL(url) } self.delegate?.tappableLabel?(self, didTapUrl: url, atRange: range) } } } } } 

Drop-in solution as a category on UILabel (this assumes your UILabel uses an attributed string with some NSLinkAttributeName attributes in it):

 @implementation UILabel (Support) - (BOOL)openTappedLinkAtLocation:(CGPoint)location { CGSize labelSize = self.bounds.size; NSTextContainer* textContainer = [[NSTextContainer alloc] initWithSize:CGSizeZero]; textContainer.lineFragmentPadding = 0.0; textContainer.lineBreakMode = self.lineBreakMode; textContainer.maximumNumberOfLines = self.numberOfLines; textContainer.size = labelSize; NSLayoutManager* layoutManager = [[NSLayoutManager alloc] init]; [layoutManager addTextContainer:textContainer]; NSTextStorage* textStorage = [[NSTextStorage alloc] initWithAttributedString:self.attributedText]; [textStorage addAttribute:NSFontAttributeName value:self.font range:NSMakeRange(0, textStorage.length)]; [textStorage addLayoutManager:layoutManager]; CGRect textBoundingBox = [layoutManager usedRectForTextContainer:textContainer]; CGPoint textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x, (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y); CGPoint locationOfTouchInTextContainer = CGPointMake(location.x - textContainerOffset.x, location.y - textContainerOffset.y); NSInteger indexOfCharacter = [layoutManager characterIndexForPoint:locationOfTouchInTextContainer inTextContainer:textContainer fractionOfDistanceBetweenInsertionPoints:nullptr]; if (indexOfCharacter >= 0) { NSURL* url = [textStorage attribute:NSLinkAttributeName atIndex:indexOfCharacter effectiveRange:nullptr]; if (url) { [[UIApplication sharedApplication] openURL:url]; return YES; } } return NO; } @end 

This generic method works too !

 func didTapAttributedTextInLabel(gesture: UITapGestureRecognizer, inRange targetRange: NSRange) -> Bool { let layoutManager = NSLayoutManager() let textContainer = NSTextContainer(size: CGSize.zero) guard let strAttributedText = self.attributedText else { return false } let textStorage = NSTextStorage(attributedString: strAttributedText) // Configure layoutManager and textStorage layoutManager.addTextContainer(textContainer) textStorage.addLayoutManager(layoutManager) // Configure textContainer textContainer.lineFragmentPadding = Constants.lineFragmentPadding textContainer.lineBreakMode = self.lineBreakMode textContainer.maximumNumberOfLines = self.numberOfLines let labelSize = self.bounds.size textContainer.size = CGSize(width: labelSize.width, height: CGFloat.greatestFiniteMagnitude) // Find the tapped character location and compare it to the specified range let locationOfTouchInLabel = gesture.location(in: self) let xCordLocationOfTouchInTextContainer = locationOfTouchInLabel.x let yCordLocationOfTouchInTextContainer = locationOfTouchInLabel.y let locOfTouch = CGPoint(x: xCordLocationOfTouchInTextContainer , y: yCordLocationOfTouchInTextContainer) let indexOfCharacter = layoutManager.characterIndex(for: locOfTouch, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil) guard let strLabel = text else { return false } let charCountOfLabel = strLabel.count if indexOfCharacter < (charCountOfLabel - 1) { return NSLocationInRange(indexOfCharacter, targetRange) } else { return false } } 

And you can call the method with

 let text = yourLabel.text let termsRange = (text as NSString).range(of: fullString) if yourLabel.didTapAttributedTextInLabel(gesture: UITapGestureRecognizer, inRange: termsRange) { showCorrespondingViewController() } 

I follow this version,

Swift 4:

 import Foundation class AELinkedClickableUILabel: UILabel { typealias YourCompletion = () -> Void var linkedRange: NSRange! var completion: YourCompletion? @objc func linkClicked(sender: UITapGestureRecognizer){ if let completionBlock = completion { let textView = UITextView(frame: self.frame) textView.text = self.text textView.attributedText = self.attributedText let index = textView.layoutManager.characterIndex(for: sender.location(in: self), in: textView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil) if linkedRange.lowerBound <= index && linkedRange.upperBound >= index { completionBlock() } } } /** * This method will be used to set an attributed text specifying the linked text with a * handler when the link is clicked */ public func setLinkedTextWithHandler(text:String, link: String, handler: @escaping ()->()) -> Bool { let attributextText = NSMutableAttributedString(string: text) let foundRange = attributextText.mutableString.range(of: link) if foundRange.location != NSNotFound { self.linkedRange = foundRange self.completion = handler attributextText.addAttribute(NSAttributedStringKey.link, value: text, range: foundRange) self.isUserInteractionEnabled = true self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(linkClicked(sender:)))) return true } return false } } 

Call Example:

 button.setLinkedTextWithHandler(text: "This website (stackoverflow.com) is awesome", link: "stackoverflow.com") { // show popup or open to link } 
  NSString *string = name; NSError *error = NULL; NSDataDetector *detector = [NSDataDetector dataDetectorWithTypes:(NSTextCheckingTypes)NSTextCheckingTypeLink | NSTextCheckingTypePhoneNumber error:&error]; NSArray *matches = [detector matchesInString:string options:0 range:NSMakeRange(0, [string length])]; for (NSTextCheckingResult *match in matches) { if (([match resultType] == NSTextCheckingTypePhoneNumber)) { NSString *phoneNumber = [match phoneNumber]; NSLog(@" Phone Number is :%@",phoneNumber); label.enabledTextCheckingTypes = NSTextCheckingTypePhoneNumber; } if(([match resultType] == NSTextCheckingTypeLink)) { NSURL *email = [match URL]; NSLog(@"Email is :%@",email); label.enabledTextCheckingTypes = NSTextCheckingTypeLink; } if (([match resultType] == NSTextCheckingTypeLink)) { NSURL *url = [match URL]; NSLog(@"URL is :%@",url); label.enabledTextCheckingTypes = NSTextCheckingTypeLink; } } label.text =name; }