234

It's trivial to make hyperlinks clickable in a UITextView. You just set the "detect links" checkbox on the view in IB, and it detects HTTP links and turns them into hyperlinks.

However, that still means that what the user sees is the "raw" link. RTF files and HTML both allow you to set up a user-readable string with a link "behind" it.

It's easy to install attributed text into a text view (or a UILabel or UITextField, for that matter.) However, when that attributed text includes a link, it is not clickable.

Is there a way to make user-readable text clickable in a UITextView, UILabel or UITextField?

The markup is different on SO, but here is the general idea. What I want is text like this:

This morph was generated with Face Dancer, Click to view in the app store.

The only thing I can get is this:

This morph was generated with Face Dancer, Click on http://example.com/facedancer to view in the app store.

Tamás Sengel
  • 55,884
  • 29
  • 169
  • 223
Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • 7
    Possible duplicate of [Create tap-able "links" in the NSAttributedText of a UILabel?](http://stackoverflow.com/questions/1256887/create-tap-able-links-in-the-nsattributedtext-of-a-uilabel) – Senseful Jan 30 '17 at 00:08
  • https://stackoverflow.com/a/65980444/286361 Added simple UITextView subclass based on [answer of Karl Nosworthy](https://stackoverflow.com/a/34014655/286361) – Vladimir Jan 31 '21 at 16:26
  • Basing on [answer of Karl Nosworthy](https://stackoverflow.com/a/28572505/286361) created a simple UITextView subclass: https://stackoverflow.com/a/65980444/286361 – Vladimir Jan 31 '21 at 16:42

23 Answers23

163

Use NSMutableAttributedString.

NSMutableAttributedString * str = [[NSMutableAttributedString alloc] initWithString:@"Google"];
[str addAttribute: NSLinkAttributeName value: @"http://www.google.com" range: NSMakeRange(0, str.length)];
yourTextView.attributedText = str;

Edit:

This is not directly about the question but just to clarify, UITextField and UILabel does not support opening URLs. If you want to use UILabel with links you can check TTTAttributedLabel.

Also you should set dataDetectorTypes value of your UITextView to UIDataDetectorTypeLink or UIDataDetectorTypeAll to open URLs when clicked. Or you can use delegate method as suggested in the comments.

ujell
  • 2,792
  • 3
  • 17
  • 23
  • 10
    Yup, it is working, simply put it inside a UITextView and override delegate method: - (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)url inRange:(NSRange)characterRange – Yunus Nedim Mehel Jul 18 '14 at 12:12
  • This doesn't work in a UILabel - nothing happens when you tap the field. – Jack BeNimble Mar 27 '15 at 04:40
  • Yes it works, you don't have to implement the delegate method, it goes to url by default. – Jack BeNimble Mar 27 '15 at 05:06
  • @ujell Is there a way to use this if you want to implement a method rather than using a url? – sabo Apr 22 '15 at 20:34
  • 7
    @saboehnke do you mean calling a method when link clicked? if so implement delegate method, give a dummy url as attribute and call your method in `- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange` – ujell Apr 23 '15 at 09:26
  • @ujell So you are saying that I need to pass into that bool method my url and if the url matches the dummy url I setup then call the method I want to call? – sabo Apr 27 '15 at 16:17
  • @ujell Because when I try to call textView.delegate = self I get an error such as: assigning to 'id uitextfielddelegate ' from incompatible type, even after adding the to the header. – sabo Apr 27 '15 at 16:40
  • @saboehnke are you sure that you have an `UITextView`? that error seems like you have a `UITextField`. – ujell Apr 27 '15 at 20:31
  • 2
    I don't know how its working. Attribute's value should be type of ````NSURL````. ---- `[str addAttribute: NSLinkAttributeName value: [NSURL URLWithString:@"http://www.google.com"] range: NSMakeRange(0, str.length)];` – Nirav Dangi Jul 23 '15 at 13:05
  • 1
    @NiravDangi from `NSAttributedString.h` `UIKIT_EXTERN NSString * const NSLinkAttributeName NS_AVAILABLE(10_0, 7_0); // NSURL (preferred) or NSString` – Ahmed Nawar Nov 17 '15 at 01:14
  • This does not answer my question. I wanted to be able to set clickable links on normal text (Not a URL string) without having to write custom code that applies an attributed string.) This is custom code. Not what I want. See my answer for a way to load the contents from an RTF file, including display text with embedded links. – Duncan C Jun 21 '16 at 13:58
162

I found this really useful but I needed to do it in quite a few places so I've wrapped my approach up in a simple extension to NSMutableAttributedString:

Swift 3

extension NSMutableAttributedString {

    public func setAsLink(textToFind:String, linkURL:String) -> Bool {

        let foundRange = self.mutableString.range(of: textToFind)
        if foundRange.location != NSNotFound {
            self.addAttribute(.link, value: linkURL, range: foundRange)
            return true
        }
        return false
    }
}

Swift 2

import Foundation

extension NSMutableAttributedString {

   public func setAsLink(textToFind:String, linkURL:String) -> Bool {

       let foundRange = self.mutableString.rangeOfString(textToFind)
       if foundRange.location != NSNotFound {
           self.addAttribute(NSLinkAttributeName, value: linkURL, range: foundRange)
           return true
       }
       return false
   }
}

Example usage:

let attributedString = NSMutableAttributedString(string:"I love stackoverflow!")
let linkWasSet = attributedString.setAsLink("stackoverflow", linkURL: "http://stackoverflow.com")

if linkWasSet {
    // adjust more attributedString properties
}

Objective-C

I've just hit a requirement to do the same in a pure Objective-C project, so here's the Objective-C category.

@interface NSMutableAttributedString (SetAsLinkSupport)

- (BOOL)setAsLink:(NSString*)textToFind linkURL:(NSString*)linkURL;

@end


@implementation NSMutableAttributedString (SetAsLinkSupport)

- (BOOL)setAsLink:(NSString*)textToFind linkURL:(NSString*)linkURL {

     NSRange foundRange = [self.mutableString rangeOfString:textToFind];
     if (foundRange.location != NSNotFound) {
         [self addAttribute:NSLinkAttributeName value:linkURL range:foundRange];
         return YES;
     }
     return NO;
}

@end

Example usage:

NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:"I love stackoverflow!"];

BOOL linkWasSet = [attributedString setAsLink:@"stackoverflow" linkURL:@"http://stackoverflow.com"];

if (linkWasSet) {
    // adjust more attributedString properties
}

Make Sure that the NSTextField's Behavior attribute is set as Selectable. Xcode NSTextField behavior attribute

sdc
  • 2,603
  • 1
  • 27
  • 40
Karl Nosworthy
  • 1,991
  • 1
  • 17
  • 16
  • @KarlNosworthy How can we add multiple links in a single String using your method ? – Umair Afzal Feb 23 '17 at 06:17
  • @umairafzal since its just setting link attributes on the string, you can just call the _setLink_ method multiple times to do multiple links on a single String. – Karl Nosworthy Feb 23 '17 at 08:04
  • 8
    This worked correctly. Just want to say that you need to make your UITextView selectable to allow the link to be clickable – lujop Mar 27 '17 at 15:58
  • I am getting "result of call to 'setAsLink(textToFind:linkURL:) is unused" unused. Any idea why? – Felecia Genet Jul 25 '17 at 22:20
  • 1
    @felecia genet, in both the Objective C and Swift implementations the method returns a boolean result to indicate if a match and resulting set took place. The error you're seeing is because you are not capturing that result - which is fine. You could either capture that result by assigning it to a local variable or adjust the method to stop it returning the boolean value if that better suits your needs. I hope that helps? – Karl Nosworthy Jul 25 '17 at 23:27
  • That does help Karl thanks for the insight. Would you mind providing an example? This is my first time using NSLinkAttributeName – Felecia Genet Jul 26 '17 at 12:30
  • 1
    No problem @feleciagenet, I've added storing and checking of the method result to both the Swift and ObjectiveC examples. – Karl Nosworthy Jul 26 '17 at 14:03
  • You need to import UIKit on the Swift extension – JP Aquino Nov 28 '17 at 15:54
  • I think swift needs to an update to `self.addAttribute(NSAttributedStringKey.link, value: linkURL, range: foundRange)`, otherwise you get an "Use of unresolved identifier 'NSLinkAttributeName'" – Jay Mutzafi May 27 '18 at 00:07
  • In addition to Selectable being the required behavior, Rich Text must also be enabled. – Barry Jones Jan 19 '22 at 01:53
34

I just created a subclass of UILabel to specially address such use cases. You can add multiple links easily and define different handlers for them. It also supports highlighting the pressed link when you touch down for touch feedback. Please refer to https://github.com/null09264/FRHyperLabel.

In your case, the code may like this:

FRHyperLabel *label = [FRHyperLabel new];

NSString *string = @"This morph was generated with Face Dancer, Click to view in the app store.";
NSDictionary *attributes = @{NSFontAttributeName: [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]};

label.attributedText = [[NSAttributedString alloc]initWithString:string attributes:attributes];

[label setLinkForSubstring:@"Face Dancer" withLinkHandler:^(FRHyperLabel *label, NSString *substring){
    [[UIApplication sharedApplication] openURL:aURL];
}];

Sample Screenshot (the handler is set to pop an alert instead of open a url in this case)

facedancer

Jinghan Wang
  • 1,139
  • 12
  • 21
  • if suppose my text is like this This morph was generated with Face Dancer, Click to Face Dancer view in the app store Face Dancer. here i am having 3 Face Dancer it was not working for it – MANCHIKANTI KRISHNAKISHORE Oct 03 '15 at 09:46
  • 1
    In this case please use the API `- (void)setLinkForRange:(NSRange)range withLinkHandler:(void(^)(FRHyperLabel *label, NSRange selectedRange))handler;` instead. Please refer to the readme in the github page. – Jinghan Wang Oct 03 '15 at 09:51
  • 1
    FRHyperLabel seems not working anymore. Inside "characterIndexForPoint:", it always return -1 (not found). – John Pang Oct 13 '17 at 09:21
  • Doesn't work for me for multiline label. Detection of characters is wrong. 15-characters link string is clickable only on some first characters, other characters do nothing – Accid Bright May 22 '18 at 14:42
28

Minor improvement to ujell's solution: If you use NSURL instead of a NSString, you can use any URL (e.g. custom urls)

NSURL *URL = [NSURL URLWithString: @"whatsapp://app"];
NSMutableAttributedString * str = [[NSMutableAttributedString alloc] initWithString:@"start Whatsapp"];
[str addAttribute: NSLinkAttributeName value:URL range: NSMakeRange(0, str.length)];
yourTextField.attributedText = str;

Have fun!

Hans One
  • 3,329
  • 27
  • 28
28

Swift 4:

var string = "Google"
var attributedString = NSMutableAttributedString(string: string, attributes:[NSAttributedStringKey.link: URL(string: "http://www.google.com")!])

yourTextView.attributedText = attributedString

Swift 3.1:

var string = "Google"
var attributedString = NSMutableAttributedString(string: string, attributes:[NSLinkAttributeName: URL(string: "http://www.google.com")!])

yourTextView.attributedText = attributedString
Artrmz
  • 340
  • 2
  • 14
Bill Chan
  • 3,199
  • 36
  • 32
  • 2
    This answer works perfectly as-is. Don't seem to need any of the coloring or custom subclasses that other answers use. – zeroimpl Dec 16 '18 at 22:12
  • 1
    You can add string before and after that link as well, `let string = NSMutableAttributedString(string: "accept these ") let attributedString = NSMutableAttributedString(string: "terms and conditions", attributes:[NSAttributedString.Key.link: URL(string: "http://www.google.com")!]) string.append(attributedString) textView.attributedText = string` – Soufian Hossam May 07 '21 at 13:49
21

I too had a similar requirement, initially I used UILabel and then I realized that UITextView is better. I made UITextView behave like UILabel by disabling interaction and scrolling and made a category method for NSMutableAttributedString to set link to text same as what Karl had done (+1 for that) this is my obj c version

-(void)setTextAsLink:(NSString*) textToFind withLinkURL:(NSString*) url
{
    NSRange range = [self.mutableString rangeOfString:textToFind options:NSCaseInsensitiveSearch];

    if (range.location != NSNotFound) {

        [self addAttribute:NSLinkAttributeName value:url range:range];
        [self addAttribute:NSForegroundColorAttributeName value:[UIColor URLColor] range:range];
    }
}

you can use the below delegate then to handle the action

- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)url inRange:(NSRange)characterRange
{
    // do the task
    return YES;
}
anoop4real
  • 7,598
  • 4
  • 53
  • 56
  • 1
    As far as I can tell setting `NSForegroundColorAttributeName` in a range where `NSLinkAttributeName` is applied does not work. No matter what, the `linkTextAttributes` of the `UITextView` are applied instead. Does `NSForegroundColorAttributeName` work for you? – Dima Apr 06 '15 at 18:17
  • Are you sure you're not also setting `linkTextAttributes` to the same thing? or perhaps `tintColor`? Are you able to make 2 links show up in different colors in the same textview? – Dima Apr 06 '15 at 18:25
  • 1
    Here is a working code NSRange range = [self.text rangeOfString:textToFind options:NSCaseInsensitiveSearch]; if (range.location != NSNotFound) { NSMutableAttributedString * string = [[NSMutableAttributedString alloc] initWithString:self.text]; [string addAttribute:NSLinkAttributeName value:url range:range]; [string addAttribute:NSForegroundColorAttributeName value:[UIColor blueColor] range:range]; self.text = @""; self.attributedText = string; } – Nosov Pavel Jul 10 '15 at 22:21
19

Use UITextView it supports clickable Links. Create attributed string using the following code

NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:strSomeTextWithLinks];

Then set UITextView text as follows

NSDictionary *linkAttributes = @{NSForegroundColorAttributeName: [UIColor redColor],

                                 NSUnderlineColorAttributeName: [UIColor blueColor],

                                 NSUnderlineStyleAttributeName: @(NSUnderlinePatternSolid)};

customTextView.linkTextAttributes = linkAttributes; // customizes the appearance of links
textView.attributedText = attributedString;

Make sure that you enable "Selectable" behavior of the UITextView in XIB.

Nitheesh George
  • 1,357
  • 10
  • 13
17

The quick answer is using UITextView instead of UILabel. You need to enable Selectable and disable Editable.

Then disable scroll indicators and bounces.

Screen Shot

Screen Shot

My solution using NSMutableAttributedString from html string NSHTMLTextDocumentType

NSString *s = @"<p><a href='https://itunes.apple.com/us/app/xxxx/xxxx?mt=8'>https://itunes.apple.com/us/app/xxxx/xxxx?mt=8</a></p>";

NSMutableAttributedString *text = [[NSMutableAttributedString alloc]
                                           initWithData: [s dataUsingEncoding:NSUnicodeStringEncoding]
                                           options: @{ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType }
                                           documentAttributes: nil
                                           error: nil
                                           ];

cell.content.attributedText = text;
Sham Dhiman
  • 1,348
  • 1
  • 21
  • 59
  • 1
    This. I was able to read an RTF file from my resource bundle, convert it to `NSAttributedString`, set it as the `attributedText` of my `UITextView` and the hyperlinks just work! It would have been a lot of work to find the range of each hyperlink and set it up using attributes. – Nicolas Miari Feb 07 '20 at 06:06
16

Swift 3 example to detect actions on attributed text taps

https://stackoverflow.com/a/44226491/5516830

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
}

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 add any action you want with shouldInteractWith URLUITextFieldDelegate method.

Cheers!!

ajw
  • 2,568
  • 23
  • 27
14

The heart of my question was that I wanted to be able to create clickable links in text views/fields/labels without having to write custom code to manipulate the text and add the links. I wanted it to be data-driven.

I finally figured out how to do it. The issue is that IB doesn't honor embedded links.

Furthermore, the iOS version of NSAttributedString doesn't let you initialize an attributed string from an RTF file. The OS X version of NSAttributedString does have an initializer that takes an RTF file as input.

NSAttributedString conforms to the NSCoding protocol, so you can convert it to/from NSData

I created an OS X command line tool that takes an RTF file as input and outputs a file with the extension .data that contains the NSData from NSCoding. I then put the .data file into my project and add a couple of lines of code that loads the text into the view. The code looks like this (this project was in Swift) :

/*
If we can load a file called "Dates.data" from the bundle and convert it to an attributed string,
install it in the dates field. The contents contain clickable links with custom URLS to select
each date.
*/
if
  let datesPath = NSBundle.mainBundle().pathForResource("Dates", ofType: "data"),
  let datesString = NSKeyedUnarchiver.unarchiveObjectWithFile(datesPath) as? NSAttributedString
{
  datesField.attributedText = datesString
}

For apps that use a lot of formatted text, I create a build rule that tells Xcode that all the .rtf files in a given folder are source and the .data files are the output. Once I do that, I simply add .rtf files to the designated directory, (or edit existing files) and the build process figures out that they are new/updated, runs the command line tool, and copies the files into the app bundle. It works beautifully.

I wrote a blog post that links to a sample (Swift) project demonstrating the technique. You can see it here:

Creating clickable URLs in a UITextField that open in your app

Duncan C
  • 128,072
  • 22
  • 173
  • 272
6

I have written a method, that adds a link(linkString) to a string (fullString) with a certain url(urlString):

- (NSAttributedString *)linkedStringFromFullString:(NSString *)fullString withLinkString:(NSString *)linkString andUrlString:(NSString *)urlString
{
    NSRange range = [fullString rangeOfString:linkString options:NSLiteralSearch];
    NSMutableAttributedString *str = [[NSMutableAttributedString alloc] initWithString:fullString];

    NSMutableParagraphStyle *paragraphStyle = NSMutableParagraphStyle.new;
    paragraphStyle.alignment = NSTextAlignmentCenter;
    NSDictionary *attributes = @{NSForegroundColorAttributeName:RGB(0x999999),
                                 NSFontAttributeName:[UIFont fontWithName:@"HelveticaNeue-Light" size:10],
                                 NSParagraphStyleAttributeName:paragraphStyle};
    [str addAttributes:attributes range:NSMakeRange(0, [str length])];
    [str addAttribute: NSLinkAttributeName value:urlString range:range];

    return str;
}

You should call it like this:

NSString *fullString = @"A man who bought the Google.com domain name for $12 and owned it for about a minute has been rewarded by Google for uncovering the flaw.";
NSString *linkString = @"Google.com";
NSString *urlString = @"http://www.google.com";

_youTextView.attributedText = [self linkedStringFromFullString:fullString withLinkString:linkString andUrlString:urlString];
Denis Kutlubaev
  • 15,320
  • 6
  • 84
  • 70
  • It is clickable but it doesn't open the link or anything. it just clicks like a button that doesn't do anything. – Reza.Ab May 31 '18 at 15:15
5

I needed to keep using a pure UILabel, so called this from my tap recognizer (this is based on malex's response here: Character index at touch point for UILabel )

UILabel* label = (UILabel*)gesture.view;
CGPoint tapLocation = [gesture locationInView:label];

// create attributed string with paragraph style from label

NSMutableAttributedString* attr = [label.attributedText mutableCopy];
NSMutableParagraphStyle* paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.alignment = label.textAlignment;

[attr addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, label.attributedText.length)];

// init text storage

NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attr];
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
[textStorage addLayoutManager:layoutManager];

// init text container

NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeMake(label.frame.size.width, label.frame.size.height+100) ];
textContainer.lineFragmentPadding  = 0;
textContainer.maximumNumberOfLines = label.numberOfLines;
textContainer.lineBreakMode        = label.lineBreakMode;

[layoutManager addTextContainer:textContainer];

// find tapped character

NSUInteger characterIndex = [layoutManager characterIndexForPoint:tapLocation
                                                  inTextContainer:textContainer
                         fractionOfDistanceBetweenInsertionPoints:NULL];

// process link at tapped character

[attr enumerateAttributesInRange:NSMakeRange(characterIndex, 1)
                                         options:0
                                      usingBlock:^(NSDictionary<NSString *,id> * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) {
                                          if (attrs[NSLinkAttributeName]) {
                                              NSString* urlString = attrs[NSLinkAttributeName];
                                              NSURL* url = [NSURL URLWithString:urlString];
                                              [[UIApplication sharedApplication] openURL:url];
                                          }
                                      }];
Community
  • 1
  • 1
masty
  • 1,539
  • 1
  • 17
  • 20
  • This was quite helpful, I was unable to get indexes from characters on the last line. Your code has the +100 on the textContainer when init'ing the CGSize, which doesn't make a whole lot of sense to me, but it did the trick. – blueether Dec 13 '16 at 04:28
5

Use UITextView and set dataDetectorTypes for Link.

like this:

testTextView.editable = false 
testTextView.dataDetectorTypes = .link

If you want to detect link, phone number,address etc..then

testTextView.dataDetectorTypes = .all
Adarsh G J
  • 2,684
  • 1
  • 24
  • 25
  • 4
    No. This only lets you make links clickable. My question is specific to making arbitrary text like "click here" clickable, not a URL like `http://somedomain/someurl?param=value` – Duncan C Sep 27 '17 at 19:56
4

Just find a code-free solution for UITextView: enter image description here

Enable Detection->Links options, the URL and also email will be detected and clickable!

Bill Chan
  • 3,199
  • 36
  • 32
  • 3
    That makes links clickable. I want to have user-readable text that has a link behind it. See the example in my original question. – Duncan C Sep 08 '15 at 20:34
  • Yes, my answer only applies to the case the link is same as the text. If the link is another stuff, I would follow @ujell's answer. – Bill Chan Sep 09 '15 at 15:18
  • 3
    My question was very specifically about clickable text that displays something other than the URL. You didn't do more than glance at the question, did you? – Duncan C Sep 10 '15 at 01:49
  • 1
    didn't serve others purpose but surely this is what i came to stack looking for... a way to make links in my chat application clickable. Bingo I found this article... thanks! Wish xcode would allow for twitter and hash tag enabling. – MizAkita Mar 17 '16 at 03:49
  • This works even with custom text insted of raw link. Remember to select Behavior -> Selectable and Detection -> Links. – krlbsk May 23 '18 at 12:38
4

Update:

There were 2 key parts to my question:

  1. How to make a link where the text shown for the clickable link is different than the actual link that is invoked:
  2. How to set up the links without having to use custom code to set the attributes on the text.

It turns out that iOS 7 added the ability to load attributed text from NSData.

I created a custom subclass of UITextView that takes advantage of the @IBInspectable attribute and lets you load contents from an RTF file directly in IB. You simply type the filename into IB and the custom class does the rest.

Here are the details:

In iOS 7, NSAttributedString gained the method initWithData:options:documentAttributes:error:. That method lets you load an NSAttributedString from an NSData object. You can first load an RTF file into NSData, then use initWithData:options:documentAttributes:error: to load that NSData into your text view. (Note that there is also a method initWithFileURL:options:documentAttributes:error: that will load an attributed string directly from a file, but that method was deprecated in iOS 9. It's safer to use the method initWithData:options:documentAttributes:error:, which wasn't deprecated.

I wanted a method that let me install clickable links into my text views without having to create any code specific to the links I was using.

The solution I came up with was to create a custom subclass of UITextView I call RTF_UITextView and give it an @IBInspectable property called RTF_Filename. Adding the @IBInspectable attribute to a property causes Interface Builder to expose that property in the "Attributes Inspector." You can then set that value from IB wihtout custom code.

I also added an @IBDesignable attribute to my custom class. The @IBDesignable attribute tells Xcode that it should install a running copy of your custom view class into Interface builder so you can see it in the graphical display of your view hierarchy. ()Unfortunately, for this class, the @IBDesignable property seems to be flaky. It worked when I first added it, but then I deleted the plain text contents of my text view and the clickable links in my view went away and I have not been able to get them back.)

The code for my RTF_UITextView is very simple. In addition to adding the @IBDesignable attribute and an RTF_Filename property with the @IBInspectable attribute, I added a didSet() method to the RTF_Filename property. The didSet() method gets called any time the value of the RTF_Filename property changes. The code for the didSet() method is quite simple:

@IBDesignable
class RTF_UITextView: UITextView
{
  @IBInspectable
  var RTF_Filename: String?
    {
    didSet(newValue)
    {
      //If the RTF_Filename is nil or the empty string, don't do anything
      if ((RTF_Filename ?? "").isEmpty)
      {
        return
      }
      //Use optional binding to try to get an URL to the
      //specified filename in the app bundle. If that succeeds, try to load
      //NSData from the file.
      if let fileURL = NSBundle.mainBundle().URLForResource(RTF_Filename, withExtension: "rtf"),
        
        //If the fileURL loads, also try to load NSData from the URL.
        let theData = NSData(contentsOfURL: fileURL)
      {
        var aString:NSAttributedString
        do
        {
          //Try to load an NSAttributedString from the data
          try
            aString = NSAttributedString(data: theData,
              options: [:],
              documentAttributes:  nil
          )
          //If it succeeds, install the attributed string into the field.
          self.attributedText = aString;
        }
        catch
        {
          print("Nerp.");
        }
      }
      
    }
  }
}

Note that if the @IBDesignable property isn't going to reliably allow you to preview your styled text in Interface builder then it might be better to set the above code up as an extension of UITextView rather than a custom subclass. That way you could use it in any text view without having to change the text view to the custom class.

See my other answer if you need to support iOS versions prior to iOS 7.

You can download a sample project that includes this new class from gitHub:

DatesInSwift demo project on Github

Community
  • 1
  • 1
Duncan C
  • 128,072
  • 22
  • 173
  • 272
3

Swift Version :

    // Attributed String for Label
    let plainText = "Apkia"
    let styledText = NSMutableAttributedString(string: plainText)
    // Set Attribuets for Color, HyperLink and Font Size
    let attributes = [NSFontAttributeName: UIFont.systemFontOfSize(14.0), NSLinkAttributeName:NSURL(string: "http://apkia.com/")!, NSForegroundColorAttributeName: UIColor.blueColor()]
    styledText.setAttributes(attributes, range: NSMakeRange(0, plainText.characters.count))
    registerLabel.attributedText = styledText
ioopl
  • 1,735
  • 19
  • 19
3

In case you're having issues with what @Karl Nosworthy and @esilver had provided above, I've updated the NSMutableAttributedString extension to its Swift 4 version.

extension NSMutableAttributedString {

public func setAsLink(textToFind:String, linkURL:String) -> Bool {

    let foundRange = self.mutableString.range(of: textToFind)
    if foundRange.location != NSNotFound {
         _ = NSMutableAttributedString(string: textToFind)
        // Set Attribuets for Color, HyperLink and Font Size
        let attributes = [NSFontAttributeName: UIFont.bodyFont(.regular, shouldResize: true), NSLinkAttributeName:NSURL(string: linkURL)!, NSForegroundColorAttributeName: UIColor.blue]

        self.setAttributes(attributes, range: foundRange)
        return true
    }
    return false
  }
}
Vick Swift
  • 2,945
  • 1
  • 15
  • 17
3

In Swift 5.5

Since Swift 5.5 NSAttributedString is completely localizable and easy to use without even defining the number of characters.

func attributedStringBasics(important: Bool) {
    var buy = AttributedString("Buy a new iPhone!")
    buy.font = .body.bold()

    var website = AttributedString("Visit Apple")
    website.font = .body.italic()
    website.link = URL(string: "http://www.apple.com")

    var container = AttributeContainer()
    if important {
        container.foregroundColor = .red
        container.underlineColor = .primary
    } else {
        container.foregroundColor = .primary
    }

    buy.mergeAttributes(container)
    website.mergeAttributes(container)

    print(buy)
    print(website)
}
2

A quick addition to Duncan C's original description vis-á-vie IB behavior. He writes: "It's trivial to make hyperlinks clickable in a UITextView. You just set the "detect links" checkbox on the view in IB, and it detects http links and turns them into hyperlinks."

My experience (at least in xcode 7) is that you also have to unclick the "Editable" behavior for the urls to be detected & clickable.

sakumatto
  • 157
  • 1
  • 9
1

If you want to use the NSLinkAttributeName in a UITextView, then you may consider using the AttributedTextView library. It's 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 })
    }
Edwin Vermeer
  • 13,017
  • 2
  • 34
  • 58
0

The excellent library from @AliSoftware OHAttributedStringAdditions makes it easy to add links in UILabel here is the documentation: https://github.com/AliSoftware/OHAttributedStringAdditions/wiki/link-in-UILabel

Community
  • 1
  • 1
iGranDav
  • 2,450
  • 1
  • 21
  • 25
0

if you want active substring in your UITextView then you can use my extended TextView... its short and simple. You can edit it as you want.

result: enter image description here

code: https://github.com/marekmand/ActiveSubstringTextView

Marek Manduch
  • 2,303
  • 1
  • 19
  • 22
0
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:strSomeTextWithLinks];

NSDictionary *linkAttributes = @{NSForegroundColorAttributeName: [UIColor redColor],   
                                 NSUnderlineColorAttributeName: [UIColor blueColor],
                                 NSUnderlineStyleAttributeName: @(NSUnderlinePatternSolid)};

customTextView.linkTextAttributes = linkAttributes; // customizes the appearance of links
textView.attributedText = attributedString;

KEY POINTS:

  • Make sure that you enable "Selectable" behavior of the UITextView in XIB.
  • Make sure that you disable "Editable" behavior of the UITextView in XIB.
Stephen Rauch
  • 47,830
  • 31
  • 106
  • 135