129

I have a UITextView which displays an NSAttributedString. This string contains words that I'd like to make tappable, such that when they are tapped I get called back so that I can perform an action. I realise that UITextView can detect taps on a URL and call back my delegate, but these aren't URLs.

It seems to me that with iOS 7 and the power of TextKit this should now be possible, however I can't find any examples and I'm not sure where to start.

I understand that it's now possible to create custom attributes in the string (although I haven't done this yet), and perhaps these will be useful to detecting if one of the magic words has been tapped? In any case, I still don't know how to intercept that tap and detect on which word the tap occurred.

Note that iOS 6 compatibility is not required.

Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
tarmes
  • 15,366
  • 10
  • 53
  • 87
  • Note: in iOS 10 and up, use the NSAttributedString.Key.link attribute instead. See my answer - however, prior to that it appears you'll have to go with the accepted answer here. – Arafangion Nov 13 '20 at 00:08

12 Answers12

121

I just wanted to help others a little more. Following on from Shmidt's response it's possible to do exactly as I had asked in my original question.

1) Create an attributed string with custom attributes applied to the clickable words. eg.

NSAttributedString* attributedString = [[NSAttributedString alloc] initWithString:@"a clickable word" attributes:@{ @"myCustomTag" : @(YES) }];
[paragraph appendAttributedString:attributedString];

2) Create a UITextView to display that string, and add a UITapGestureRecognizer to it. Then handle the tap:

- (void)textTapped:(UITapGestureRecognizer *)recognizer
{
    UITextView *textView = (UITextView *)recognizer.view;

    // Location of the tap in text-container coordinates

    NSLayoutManager *layoutManager = textView.layoutManager;
    CGPoint location = [recognizer locationInView:textView];
    location.x -= textView.textContainerInset.left;
    location.y -= textView.textContainerInset.top;

    // Find the character that's been tapped on

    NSUInteger characterIndex;
    characterIndex = [layoutManager characterIndexForPoint:location
                                           inTextContainer:textView.textContainer
                  fractionOfDistanceBetweenInsertionPoints:NULL];

    if (characterIndex < textView.textStorage.length) {

        NSRange range;
        id value = [textView.attributedText attribute:@"myCustomTag" atIndex:characterIndex effectiveRange:&range];

        // Handle as required...

        NSLog(@"%@, %d, %d", value, range.location, range.length);

    }
}

So easy when you know how!

tarmes
  • 15,366
  • 10
  • 53
  • 87
  • How would you solve this in IOS 6 ? Can you please take a look at this question ?http://stackoverflow.com/questions/19837522/line-up-three-uilabels-next-to-each-other-and-each-label-can-be-multiline?noredirect=1#comment29503448_19837522 – Steaphann Nov 08 '13 at 14:28
  • Actually characterIndexForPoint:inTextContainer: fractionOfDistanceBetweenInsertionPoints is available on iOS 6, so I think it should work. Let us know! See this project for an example: https://github.com/laevandus/NSTextFieldHyperlinks/blob/master/NSTextFieldHyperlinks/AppDelegate.m – tarmes Nov 08 '13 at 16:37
  • Documentation says it's only available in IOS 7 or later :) – Steaphann Nov 09 '13 at 08:54
  • 1
    Yes, sorry. I was getting myself confused with Mac OS! This is iOS7 only. – tarmes Nov 09 '13 at 10:41
  • It doesn't seem to work, when you have not-selectable UITextView – Paweł Brewczynski Mar 02 '14 at 22:37
  • But this isn't the answer to your own question. You asked for tappable words. Just return NO from `- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange` and do there whatever you want to do. You don't need all this position calculation – tomk Mar 25 '14 at 09:48
  • I need certain words to be tappable. That's exactly what this code does. And it is the answer to my own question. – tarmes Mar 25 '14 at 11:01
  • Please not this is a *UITextView* not an UILabel which is what I thought it was for a while and ran into a lot of problems. – Chris Harrison Apr 29 '14 at 11:09
  • For this to work in a `UITableViewCell` make sure the `UITextView` is set to `Selectable` as @PaulBrewczynski mentioned – Lukas Jan 15 '15 at 17:43
  • This does not work for iOS 9 @all. Works like charm in iOS 7, iOS 8 though. Tap doesn't work for another attributed string in the same textfield. – Dheeraj Jami Sep 24 '15 at 16:38
  • @KevinJantzer Does having the textview in a tableviewcell in iOS 9 cause any problem? – Dheeraj Jami Oct 21 '15 at 21:25
  • @DheerajJami - I'm not sure, I'm not using it in a `tableviewcell` – Kevin Jantzer Oct 21 '15 at 22:16
  • What's the preferred way? to use the nslinkattribute name or check the tap target manually in this method? – MichaelGofron Mar 05 '18 at 16:20
68

Detecting taps on attributed text with Swift

Sometimes for beginners it is a little hard to know how to do get things set up (it was for me anyway), so this example is a little fuller.

Add a UITextView to your project.

Outlet

Connect the UITextView to the ViewController with an outlet named textView.

Custom attribute

We are going to make a custom attribute by making an Extension.

Note: This step is technically optional, but if you don't do it you will need to edit the code in the next part to use a standard attribute like NSAttributedString.Key.foregroundColor. The advantage of using a custom attribute is that you can define what values you want to store in the attributed text range.

Add a new swift file with File > New > File... > iOS > Source > Swift File. You can call it what you want. I am calling mine NSAttributedStringKey+CustomAttribute.swift.

Paste in the following code:

import Foundation

extension NSAttributedString.Key {
    static let myAttributeName = NSAttributedString.Key(rawValue: "MyCustomAttribute")
}

Code

Replace the code in ViewController.swift with the following. Note the UIGestureRecognizerDelegate.

import UIKit
class ViewController: UIViewController, UIGestureRecognizerDelegate {

    @IBOutlet weak var textView: UITextView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Create an attributed string
        let myString = NSMutableAttributedString(string: "Swift attributed text")

        // Set an attribute on part of the string
        let myRange = NSRange(location: 0, length: 5) // range of "Swift"
        let myCustomAttribute = [ NSAttributedString.Key.myAttributeName: "some value"]
        myString.addAttributes(myCustomAttribute, range: myRange)

        textView.attributedText = myString

        // Add tap gesture recognizer to Text View
        let tap = UITapGestureRecognizer(target: self, action: #selector(myMethodToHandleTap(_:)))
        tap.delegate = self
        textView.addGestureRecognizer(tap)
    }

    @objc func myMethodToHandleTap(_ sender: UITapGestureRecognizer) {

        let myTextView = sender.view as! UITextView
        let layoutManager = myTextView.layoutManager

        // location of tap in myTextView coordinates and taking the inset into account
        var location = sender.location(in: myTextView)
        location.x -= myTextView.textContainerInset.left;
        location.y -= myTextView.textContainerInset.top;

        // character index at tap location
        let characterIndex = layoutManager.characterIndex(for: location, in: myTextView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)

        // if index is valid then do something.
        if characterIndex < myTextView.textStorage.length {

            // print the character index
            print("character index: \(characterIndex)")

            // print the character at the index
            let myRange = NSRange(location: characterIndex, length: 1)
            let substring = (myTextView.attributedText.string as NSString).substring(with: myRange)
            print("character at index: \(substring)")

            // check if the tap location has a certain attribute
            let attributeName = NSAttributedString.Key.myAttributeName
            let attributeValue = myTextView.attributedText?.attribute(attributeName, at: characterIndex, effectiveRange: nil)
            if let value = attributeValue {
                print("You tapped on \(attributeName.rawValue) and the value is: \(value)")
            }

        }
    }
}

enter image description here

Now if you tap on the "w" of "Swift", you should get the following result:

character index: 1
character at index: w
You tapped on MyCustomAttribute and the value is: some value

Notes

  • Here I used a custom attribute, but it could have just as easily been NSAttributedString.Key.foregroundColor (text color) that has a value of UIColor.green.
  • Formerly the text view could not be editable or selectable, but in my updated answer for Swift 4.2 it seems to be working fine no matter whether these are selected or not.

Further study

This answer was based on several other answers to this question. Besides these, see also

Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
  • use `myTextView.textStorage` instead of `myTextView.attributedText.string` – fatihyildizhan Sep 08 '15 at 14:23
  • Detecting tap through tap gesture in iOS 9 doesnt work for successive taps. Any updates on that? – Dheeraj Jami Sep 24 '15 at 18:46
  • @DheerajJami: I made this example using iOS 9 and successive taps worked for me, so I don't know what to tell you. You might try opening your own question with more details. If you get an answer (or you have already found an answer), leave a comment here so that others can benefit. – Suragch Sep 25 '15 at 11:23
  • I am using objective c with similar approach. And, successive taps for different attributed objects doesn't work for me. A keyboard shows when you tap on a attributed string, but when you tap on another attributed string with a keyboard showing, the second attributed string doesn't call the tapped gesture recognizer method. – Dheeraj Jami Sep 25 '15 at 13:45
  • @Suragch: So in iOS 9 I have checked the tapped gesture, it works without the keyboard, but when I have the keyboard showing, the tap doesn't get recognized for subsequent clicks. – Dheeraj Jami Sep 25 '15 at 13:55
  • @DheerajJami Did you find any solution for iOS 9? I am also facing the same problem when the keyboard is showing the tap doesn't get recognized. But its working fine in iOS 8. – Waqas Mahmood Nov 12 '15 at 08:00
  • 1
    @WaqasMahmood, I started [a new question](http://stackoverflow.com/questions/33667570/detecting-taps-on-attributed-text-in-a-uitextview-while-the-keyboard-is-showing) for this issue. You can star it and check back later for any answers. Feel free to edit that question or add comments if there are any more pertinent details. – Suragch Nov 12 '15 at 08:58
  • @WaqasMahmood There is [a new answer on the question I asked for you](http://stackoverflow.com/a/36473564/3681880). If this is a solution to your problem, please leave a comment there and I will mark it as solved. – Suragch Apr 07 '16 at 11:02
  • I am getting the taps for every character. But from the end of the last word until the end of the textview, I am getting the last letter. How can I avoid it ? @Suragch – PoolHallJunkie Sep 07 '16 at 11:26
  • @Suragch.. this one is detecting for only first . i want to detect in middle . – Uma Madhavi Oct 21 '16 at 13:17
  • I need more than 1 line and that causes some problems on text detection. I think its the same that mentioned @PoolHallJunkie .Any ideas for checking string's height too? – dejix Nov 03 '16 at 10:17
  • 1
    @dejix I resolve the problem by adding each time another " " empty string to the end of my TextView. That way the detection stops after your last word. Hope it helps – PoolHallJunkie Nov 03 '16 at 14:08
  • Using word "my" in method names says about "quality" of the code. – Alexander Volkov Oct 09 '17 at 19:54
  • @AlexanderVolkov, I don't recommend using "my" in production code, but I use it in teaching examples to make it very clear that it is not any sort of system variable but one that we created ourselves. (In the past when learning a new concept I've had trouble making that distinction with other people's code.) – Suragch Oct 10 '17 at 00:29
  • @suragch This is ridiculous. I think `let ` clearly indicates that the var is defined in code. `my` prefix just makes it harder to read the code. It's one of the indications of duty code. – Alexander Volkov Oct 10 '17 at 02:42
  • @suragch also the solution is not complete. It's not working without a fix that allows all gesture recognizers to process. – Alexander Volkov Oct 10 '17 at 02:44
  • 1
    Works perfectly with multiple taps, I just put in a short routine to prove this: if characterIndex < 12 { textView.textColor = UIColor.magenta }else{ textView.textColor = UIColor.blue } Really clear & simple code – Jeremy Andrews Mar 17 '19 at 09:31
32

This is a slightly modified version, building off of @tarmes answer. I couldn't get the valuevariable to return anything but null without the tweak below. Also, I needed the full attribute dictionary returned in order to determine the resulting action. I would have put this in the comments but don't appear to have the rep to do so. Apologies in advance if I have violated protocol.

Specific tweak is to use textView.textStorage instead of textView.attributedText. As a still learning iOS programmer, I am not really sure why this is, but perhaps someone else can enlighten us.

Specific modification in the tap handling method:

    NSDictionary *attributesOfTappedText = [textView.textStorage attributesAtIndex:characterIndex effectiveRange:&range];

Full code in my view controller

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.textView.attributedText = [self attributedTextViewString];
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(textTapped:)];

    [self.textView addGestureRecognizer:tap];
}  

- (NSAttributedString *)attributedTextViewString
{
    NSMutableAttributedString *paragraph = [[NSMutableAttributedString alloc] initWithString:@"This is a string with " attributes:@{NSForegroundColorAttributeName:[UIColor blueColor]}];

    NSAttributedString* attributedString = [[NSAttributedString alloc] initWithString:@"a tappable string"
                                                                       attributes:@{@"tappable":@(YES),
                                                                                    @"networkCallRequired": @(YES),
                                                                                    @"loadCatPicture": @(NO)}];

    NSAttributedString* anotherAttributedString = [[NSAttributedString alloc] initWithString:@" and another tappable string"
                                                                              attributes:@{@"tappable":@(YES),
                                                                                           @"networkCallRequired": @(NO),
                                                                                           @"loadCatPicture": @(YES)}];
    [paragraph appendAttributedString:attributedString];
    [paragraph appendAttributedString:anotherAttributedString];

    return [paragraph copy];
}

- (void)textTapped:(UITapGestureRecognizer *)recognizer
{
    UITextView *textView = (UITextView *)recognizer.view;

    // Location of the tap in text-container coordinates

    NSLayoutManager *layoutManager = textView.layoutManager;
    CGPoint location = [recognizer locationInView:textView];
    location.x -= textView.textContainerInset.left;
    location.y -= textView.textContainerInset.top;

    NSLog(@"location: %@", NSStringFromCGPoint(location));

    // Find the character that's been tapped on

    NSUInteger characterIndex;
    characterIndex = [layoutManager characterIndexForPoint:location
                                       inTextContainer:textView.textContainer
              fractionOfDistanceBetweenInsertionPoints:NULL];

    if (characterIndex < textView.textStorage.length) {

        NSRange range;
        NSDictionary *attributes = [textView.textStorage attributesAtIndex:characterIndex effectiveRange:&range];
        NSLog(@"%@, %@", attributes, NSStringFromRange(range));

        //Based on the attributes, do something
        ///if ([attributes objectForKey:...)] //make a network call, load a cat Pic, etc

    }
}
natenash203
  • 699
  • 6
  • 15
26

Making custom link and doing what you want on the tap has become much easier with iOS 7. There is very good example at Ray Wenderlich

Aditya Mathur
  • 1,185
  • 17
  • 27
13

WWDC 2013 example:

NSLayoutManager *layoutManager = textView.layoutManager;
 CGPoint location = [touch locationInView:textView];
 NSUInteger characterIndex;
 characterIndex = [layoutManager characterIndexForPoint:location
inTextContainer:textView.textContainer
fractionOfDistanceBetweenInsertionPoints:NULL];
if (characterIndex < textView.textStorage.length) { 
// valid index
// Find the word range here
// using -enumerateSubstringsInRange:options:usingBlock:
}
Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
Shmidt
  • 16,436
  • 18
  • 88
  • 136
11

I was able to solve this pretty simply with NSLinkAttributeName

Swift 2

class MyClass: UIViewController, UITextViewDelegate {

  @IBOutlet weak var tvBottom: UITextView!

  override func viewDidLoad() {
      super.viewDidLoad()

     let attributedString = NSMutableAttributedString(string: "click me ok?")
     attributedString.addAttribute(NSLinkAttributeName, value: "cs://moreinfo", range: NSMakeRange(0, 5))
     tvBottom.attributedText = attributedString
     tvBottom.delegate = self

  }

  func textView(textView: UITextView, shouldInteractWithURL URL: NSURL, inRange characterRange: NSRange) -> Bool {
      UtilityFunctions.alert("clicked", message: "clicked")
      return false
  }

}
Jase Whatson
  • 4,179
  • 5
  • 36
  • 45
  • 1
    You should check that your URL was tapped and not another URL with `if URL.scheme == "cs"` and `return true` outside of the `if` statement so the `UITextView` can handle normal `https://` links that are tapped – Daniel Storm Apr 25 '16 at 19:25
  • I did that and it worked reasonably well on iPhone 6 and 6+ but didn't work at all on iPhone 5. Went with Suragch solution above, which just works. Never found out why iPhone 5 would have a problem with this, made no sense. – n13 Dec 21 '16 at 05:44
11

Complete example for detect actions on attributed text with Swift 3

let termsAndConditionsURL = TERMS_CONDITIONS_URL;
let privacyURL            = PRIVACY_URL;

override func viewDidLoad() {
    super.viewDidLoad()

    self.txtView.delegate = self
    let str = "By continuing, you accept the Terms of use and Privacy policy"
    let attributedString = NSMutableAttributedString(string: str)
    var foundRange = attributedString.mutableString.range(of: "Terms of use") //mention the parts of the attributed text you want to tap and get an custom action
    attributedString.addAttribute(NSLinkAttributeName, value: termsAndConditionsURL, range: foundRange)
    foundRange = attributedString.mutableString.range(of: "Privacy policy")
    attributedString.addAttribute(NSLinkAttributeName, value: privacyURL, range: foundRange)
    txtView.attributedText = attributedString
}

And then you can catch the action with shouldInteractWith URL UITextViewDelegate delegate method.So make sure you have set the delegate properly.

func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let vc = storyboard.instantiateViewController(withIdentifier: "WebView") as! SKWebViewController

        if (URL.absoluteString == termsAndConditionsURL) {
            vc.strWebURL = TERMS_CONDITIONS_URL
            self.navigationController?.pushViewController(vc, animated: true)
        } else if (URL.absoluteString == privacyURL) {
            vc.strWebURL = PRIVACY_URL
            self.navigationController?.pushViewController(vc, animated: true)
        }
        return false
    }

Like wise you can perform any action according to your requirement.

Cheers!!

ajw
  • 2,568
  • 23
  • 27
4

It's possible to do that with characterIndexForPoint:inTextContainer:fractionOfDistanceBetweenInsertionPoints:. It'll work somewhat differently than you wanted - you'll have to test if a tapped character belongs to a magic word. But it shouldn't be complicated.

BTW I highly recommend watching Introducing Text Kit from WWDC 2013.

Arek Holko
  • 8,966
  • 4
  • 28
  • 46
4

With Swift 5 and iOS 12, you can create a subclass of UITextView and override point(inside:with:) with some TextKit implementation in order to make only some NSAttributedStrings in it tappable.


The following code shows how to create a UITextView that only reacts to taps on underlined NSAttributedStrings in it:

InteractiveUnderlinedTextView.swift

import UIKit

class InteractiveUnderlinedTextView: UITextView {

    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)
        configure()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        configure()
    }

    func configure() {
        isScrollEnabled = false
        isEditable = false
        isSelectable = false
        isUserInteractionEnabled = true
    }

    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        let superBool = super.point(inside: point, with: event)

        let characterIndex = layoutManager.characterIndex(for: point, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
        guard characterIndex < textStorage.length else { return false }
        let attributes = textStorage.attributes(at: characterIndex, effectiveRange: nil)

        return superBool && attributes[NSAttributedString.Key.underlineStyle] != nil
    }

}

ViewController.swift

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let linkTextView = InteractiveUnderlinedTextView()
        linkTextView.backgroundColor = .orange

        let mutableAttributedString = NSMutableAttributedString(string: "Some text\n\n")
        let attributes = [NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue]
        let underlinedAttributedString = NSAttributedString(string: "Some other text", attributes: attributes)
        mutableAttributedString.append(underlinedAttributedString)
        linkTextView.attributedText = mutableAttributedString

        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(underlinedTextTapped))
        linkTextView.addGestureRecognizer(tapGesture)

        view.addSubview(linkTextView)
        linkTextView.translatesAutoresizingMaskIntoConstraints = false
        linkTextView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        linkTextView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        linkTextView.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor).isActive = true

    }

    @objc func underlinedTextTapped(_ sender: UITapGestureRecognizer) {
        print("Hello")
    }

}
Imanou Petit
  • 89,880
  • 29
  • 256
  • 218
3

Use this extension for Swift:

import UIKit

extension UITapGestureRecognizer {

    func didTapAttributedTextInTextView(textView: UITextView, inRange targetRange: NSRange) -> Bool {
        let layoutManager = textView.layoutManager
        let locationOfTouch = self.location(in: textView)
        let index = layoutManager.characterIndex(for: locationOfTouch, in: textView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
        
        return NSLocationInRange(index, targetRange)
    }
}

Add UITapGestureRecognizer to your text view with following selector:

guard let text = textView.attributedText?.string else {
        return
}
let textToTap = "Tap me"
if let range = text.range(of: textToTap),
      tapGesture.didTapAttributedTextInTextView(textView: textTextView, inRange: NSRange(range, in: text)) {
                // Tap recognized
}
Vlad Bruno
  • 327
  • 3
  • 7
Mol0ko
  • 2,938
  • 1
  • 18
  • 45
1

This one might work OK with short link, multilink in a textview. It work OK with iOS 6,7,8.

- (void)tappedTextView:(UITapGestureRecognizer *)tapGesture {
    if (tapGesture.state != UIGestureRecognizerStateEnded) {
        return;
    }
    UITextView *textView = (UITextView *)tapGesture.view;
    CGPoint tapLocation = [tapGesture locationInView:textView];

    NSDataDetector *detector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink|NSTextCheckingTypePhoneNumber
                                                           error:nil];
    NSArray* resultString = [detector matchesInString:self.txtMessage.text options:NSMatchingReportProgress range:NSMakeRange(0, [self.txtMessage.text length])];
    BOOL isContainLink = resultString.count > 0;

    if (isContainLink) {
        for (NSTextCheckingResult* result in  resultString) {
            CGRect linkPosition = [self frameOfTextRange:result.range inTextView:self.txtMessage];

            if(CGRectContainsPoint(linkPosition, tapLocation) == 1){
                if (result.resultType == NSTextCheckingTypePhoneNumber) {
                    NSString *phoneNumber = [@"telprompt://" stringByAppendingString:result.phoneNumber];
                    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:phoneNumber]];
                }
                else if (result.resultType == NSTextCheckingTypeLink) {
                    [[UIApplication sharedApplication] openURL:result.URL];
                }
            }
        }
    }
}

 - (CGRect)frameOfTextRange:(NSRange)range inTextView:(UITextView *)textView
{
    UITextPosition *beginning = textView.beginningOfDocument;
    UITextPosition *start = [textView positionFromPosition:beginning offset:range.location];
    UITextPosition *end = [textView positionFromPosition:start offset:range.length];
    UITextRange *textRange = [textView textRangeFromPosition:start toPosition:end];
    CGRect firstRect = [textView firstRectForRange:textRange];
    CGRect newRect = [textView convertRect:firstRect fromView:textView.textInputView];
    return newRect;
}
Tony TRAN
  • 2,118
  • 1
  • 15
  • 16
1

This has changed as of iOS 10. In iOS 10, you can use the .link attribute and it all just works.

No need for custom attributes, tap gesture recognisers or anything. It works like an ordinary URL.

To do this, instead of adding the url to the NSMutableAttributedString, add what you want to call the url instead (eg, 'cats' to go to the wikipedia page about cats), and then add the standard attribute NSAttributedString.Key.link (I'm using Swift here), with the NSURL containing the target URL.

Reference: https://medium.com/real-solutions-artificial-intelligence/create-clickable-links-with-nsmutableattributedstring-12b6661a357d

Arafangion
  • 11,517
  • 1
  • 40
  • 72