OdieO
OdieO

Reputation: 7004

Make link in UILabel.attributedText *not* blue and *not* underlined

I want some words within my OHAttributedLabel to be links, but I want them to be colors other than blue and I don't want the underline.

This is giving me a blue link with underlined text:

 -(void)createLinkFromWord:(NSString*)word withColor:(UIColor*)color atRange:(NSRange)range{

    NSMutableAttributedString* mutableAttributedText = [self.label.attributedText mutableCopy];   

    [mutableAttributedText beginEditing];
    [mutableAttributedText addAttribute:kOHLinkAttributeName
                   value:[NSURL URLWithString:@"http://www.somewhere.net"]
                   range:range];

    [mutableAttributedText addAttribute:(id)kCTForegroundColorAttributeName
                   value:color
                   range:range];

    [mutableAttributedText addAttribute:(id)kCTUnderlineStyleAttributeName
                   value:[NSNumber numberWithInt:kCTUnderlineStyleNone]
                   range:range];
    [mutableAttributedText endEditing];


    self.label.attributedText = mutableAttributedText;

}

Since I'm using OHAttributedLabel, I also tried using the methods in it's NSAttributedString+Attributes.h category, but those return blue underlined links as well:

-(void)createLinkFromWord:(NSString*)word withColor:(UIColor*)color atRange:(NSRange)range{

NSMutableAttributedString* mutableAttributedText = [self.label.attributedText mutableCopy];

[mutableAttributedText setLink:[NSURL URLWithString:@"http://www.somewhere.net"] range:range];
[mutableAttributedText setTextColor:color range:range];
[mutableAttributedText setTextUnderlineStyle:kCTUnderlineStyleNone range:range];

self.label.attributedText = mutableAttributedText;
}

If I comment out the line setting the links in each version, the text gets colored to what I pass in - that works. It just seems like setting the link is overriding this and turning it back to blue.

Unfortunately the apple docs page I found shows how to set the link text to blue and underline it, exactly what I don't need: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/AttributedStrings/Tasks/ChangingAttrStrings.html

Upvotes: 44

Views: 36271

Answers (11)

Alexander Volkov
Alexander Volkov

Reputation: 8387

It's better to use UITextView with "Link" feature enabled. In this case you can do it with one line:

Swift 4:

// apply link attributes to label.attributedString, then
textView.tintColor = UIColor.red // any color you want

Full example:

    let attributedString = NSMutableAttributedString(string: "Here is my link")
    let range = NSRange(location: 7, length:4)
    attributedString.addAttribute(.link, value: "http://google.com", range: range)
    attributedString.addAttribute(.underlineStyle, value: 1, range: range)
    attributedString.addAttribute(.underlineColor, value: UIColor.red, range: range)
    textView.tintColor = UIColor.red // any color you want

Or you can apply attributes to links only:

  textView.linkTextAttributes = [
      .foregroundColor: UIColor.red
      .underlineStyle: 1,
      .underlineColor: UIColor.red
   ]

Upvotes: -2

Tul
Tul

Reputation: 159

For Swift 3 following way worked out for me using TTTAttributedLabel:

1) Add a label on the storyboard and define its class to be TTTAttributedLabel Storyboard view

2) In code define @IBOutlet var termsLabel: TTTAttributedLabel!

3) Then in ViewDidLoad write these lines

termsLabel.attributedText = NSAttributedString(string: "By using this app you agree to the Privacy Policy & Terms & Conditions.")
guard let labelString = termsLabel.attributedText else {
            return
        }
        guard let privacyRange = labelString.string.range(of: "Privacy Policy") else {
            return
        }
        guard let termsConditionRange = labelString.string.range(of: "Terms & Conditions") else {
            return
        }

        let privacyNSRange: NSRange = labelString.string.nsRange(from: privacyRange)
        let termsNSRange: NSRange = labelString.string.nsRange(from: termsConditionRange)

        termsLabel.addLink(to: URL(string: "privacy"), with: privacyNSRange)
        termsLabel.addLink(to: URL(string: "terms"), with: termsNSRange)

        termsLabel.delegate = self
        let attributedText = NSMutableAttributedString(attributedString: termsLabel.attributedText!)
        attributedText.addAttributes([NSFontAttributeName : UIFont(name: "Roboto-Medium", size: 12)!], range: termsNSRange)
        attributedText.addAttributes([NSFontAttributeName : UIFont(name: "Roboto-Medium", size: 12)!], range: privacyNSRange)
        attributedText.addAttributes([kCTForegroundColorAttributeName as String: UIColor.orange], range: termsNSRange)
        attributedText.addAttributes([kCTForegroundColorAttributeName as String: UIColor.green], range: privacyNSRange)
        attributedText.addAttributes([NSUnderlineStyleAttributeName: NSUnderlineStyle.styleNone.rawValue], range: termsNSRange)
        attributedText.addAttributes([NSUnderlineStyleAttributeName: NSUnderlineStyle.styleNone.rawValue], range: privacyNSRange)
        termsLabel.attributedText = attributedText

It would look like this Simulator View

4) Finally write the delegate function of TTTAttributedLabel so that you can open links on tap

public func attributedLabel(_ label: TTTAttributedLabel!, didSelectLinkWith url: URL!) {
    switch url.absoluteString  {
    case "privacy":
        SafariBrowser.open("http://google.com", presentingViewController: self)
    case "terms":
        SafariBrowser.open("http://google.com", presentingViewController: self)
    default:
        break
    }
}

Update for Swift 4.2

For Swift 4.2, there are some changes in step 3, all other steps would remain same as above:

3) In ViewDidLoad write these lines

termsLabel.attributedText = NSAttributedString(string: "By using this app you agree to the Privacy Policy & Terms & Conditions.")
guard let labelString = termsLabel.attributedText else {
            return
        }
        guard let privacyRange = labelString.string.range(of: "Privacy Policy") else {
            return
        }
        guard let termsConditionRange = labelString.string.range(of: "Terms & Conditions") else {
            return
        }

        let privacyNSRange: NSRange = labelString.string.nsRange(from: privacyRange)
        let termsNSRange: NSRange = labelString.string.nsRange(from: termsConditionRange)

        termsLabel.addLink(to: URL(string: "privacy"), with: privacyNSRange)
        termsLabel.addLink(to: URL(string: "terms"), with: termsNSRange)

        termsLabel.delegate = self
        let attributedText = NSMutableAttributedString(attributedString: termsLabel.attributedText!)
        attributedText.addAttributes([NSAttributedString.Key.font : UIFont(name: "Roboto-Regular", size: 12)!], range: termsNSRange)
        attributedText.addAttributes([NSAttributedString.Key.font : UIFont(name: "Roboto-Regular", size: 12)!], range: privacyNSRange)
        attributedText.addAttributes([kCTForegroundColorAttributeName as NSAttributedString.Key : UIColor.orange], range: termsNSRange)
        attributedText.addAttributes([kCTForegroundColorAttributeName as NSAttributedString.Key : UIColor.green], range: privacyNSRange)
    attributedText.addAttributes([NSAttributedString.Key.underlineStyle: 0], range: termsNSRange)
        attributedText.addAttributes([NSAttributedString.Key.underlineStyle: 0], range: privacyNSRange)

Upvotes: 1

Lucas Chwe
Lucas Chwe

Reputation: 2768

If you're using a UITextView, you might have to change the tintColor property to change the link color.

Upvotes: 13

guru
guru

Reputation: 2817

Swift 4.0 :

Short and simple

 let LinkAttributes = NSMutableDictionary(dictionary: testLink.linkAttributes)
 LinkAttributes[NSAttributedStringKey.underlineStyle] =  NSNumber(value: false)
 LinkAttributes[NSAttributedStringKey.foregroundColor] = UIColor.black // Whatever your label color
 testLink.linkAttributes = LinkAttributes as NSDictionary as! [AnyHashable: Any]

Upvotes: -1

pchelnikov
pchelnikov

Reputation: 434

Swift 2.3 example for TTTAttributedLabel:

yourLabel.linkAttributes       = [
    NSForegroundColorAttributeName: UIColor.grayColor(),
    NSUnderlineStyleAttributeName: NSNumber(bool: true)
]
yourLabel.activeLinkAttributes = [
    NSForegroundColorAttributeName: UIColor.grayColor().colorWithAlphaComponent(0.8),
    NSUnderlineStyleAttributeName: NSNumber(bool: false)
]

Swift 4

yourLabel.linkAttributes = [
    .foregroundColor: UIColor.grayColor(),
    .underlineStyle: NSNumber(value: true)
]
yourLabel.activeLinkAttributes = [
    .foregroundColor: UIColor.grayColor().withAlphaComponent(0.7),
    .underlineStyle: NSNumber(value: false)
]

Upvotes: 4

Allison
Allison

Reputation: 2383

If you're like me and really don't want to use TTT (or need it in your own custom implementation where you're drawing in other weird ways):

First, subclass NSLayoutManager and then override as follows:

- (void)showCGGlyphs:(const CGGlyph *)glyphs 
           positions:(const CGPoint *)positions 
               count:(NSUInteger)glyphCount
                font:(UIFont *)font 
              matrix:(CGAffineTransform)textMatrix 
          attributes:(NSDictionary *)attributes
           inContext:(CGContextRef)graphicsContext
{
   UIColor *foregroundColor = attributes[NSForegroundColorAttributeName];

   if (foregroundColor)
   {
      CGContextSetFillColorWithColor(graphicsContext, foregroundColor.CGColor);
   }

   [super showCGGlyphs:glyphs 
             positions:positions 
                 count:glyphCount 
                  font:font 
                matrix:textMatrix 
            attributes:attributes 
             inContext:graphicsContext];
}

This is more or less telling the layout manager to actually respect NSForegroundColorAttributeName from your attributed string always instead of the weirdness Apple has internally for links.

If all you need to do is get a layout manager which draws correctly (as I needed), you can stop here. If you need an actually UILabel, it's painful but possible.

First, again, subclass UILabel and slap in all of these methods.

- (NSTextStorage *)textStorage
{
   if (!_textStorage)
   {
      _textStorage = [[NSTextStorage alloc] init];
      [_textStorage addLayoutManager:self.layoutManager];
      [self.layoutManager setTextStorage:_textStorage];
   }

   return _textStorage;
}

- (NSTextContainer *)textContainer
{
   if (!_textContainer)
   {
      _textContainer = [[NSTextContainer alloc] init];
      _textContainer.lineFragmentPadding = 0;
      _textContainer.maximumNumberOfLines = self.numberOfLines;
      _textContainer.lineBreakMode = self.lineBreakMode;
      _textContainer.widthTracksTextView = YES;
      _textContainer.size = self.frame.size;

      [_textContainer setLayoutManager:self.layoutManager];
   }

   return _textContainer;
}

- (NSLayoutManager *)layoutManager
{
   if (!_layoutManager)
   {
      // Create a layout manager for rendering
      _layoutManager = [[PRYLayoutManager alloc] init];
      _layoutManager.delegate = self;
      [_layoutManager addTextContainer:self.textContainer];
   }

   return _layoutManager;
}

- (void)layoutSubviews
{
   [super layoutSubviews];

   // Update our container size when the view frame changes
   self.textContainer.size = self.bounds.size;
}

- (void)setFrame:(CGRect)frame
{
   [super setFrame:frame];

   CGSize size = frame.size;
   size.width = MIN(size.width, self.preferredMaxLayoutWidth);
   size.height = 0;
   self.textContainer.size = size;
}

- (void)setBounds:(CGRect)bounds
{
   [super setBounds:bounds];

   CGSize size = bounds.size;
   size.width = MIN(size.width, self.preferredMaxLayoutWidth);
   size.height = 0;
   self.textContainer.size = size;
}

- (void)setPreferredMaxLayoutWidth:(CGFloat)preferredMaxLayoutWidth
{
   [super setPreferredMaxLayoutWidth:preferredMaxLayoutWidth];

   CGSize size = self.bounds.size;
   size.width = MIN(size.width, self.preferredMaxLayoutWidth);
   self.textContainer.size = size;
}
- (CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines
{
  // Use our text container to calculate the bounds required. First save our
  // current text container setup
  CGSize savedTextContainerSize = self.textContainer.size;
  NSInteger savedTextContainerNumberOfLines = self.textContainer.maximumNumberOfLines;

  // Apply the new potential bounds and number of lines
  self.textContainer.size = bounds.size;
  self.textContainer.maximumNumberOfLines = numberOfLines;

  // Measure the text with the new state
  CGRect textBounds;
  @try
  {
      NSRange glyphRange = [self.layoutManager 
                            glyphRangeForTextContainer:self.textContainer];
      textBounds = [self.layoutManager boundingRectForGlyphRange:glyphRange
                                       inTextContainer:self.textContainer];

      // Position the bounds and round up the size for good measure
      textBounds.origin = bounds.origin;
      textBounds.size.width = ceilf(textBounds.size.width);
      textBounds.size.height = ceilf(textBounds.size.height);
  }
  @finally
  {
      // Restore the old container state before we exit under any circumstances
      self.textContainer.size = savedTextContainerSize;
      self.textContainer.maximumNumberOfLines = savedTextContainerNumberOfLines;
   }

   return textBounds;
}

- (void)setAttributedText:(NSAttributedString *)attributedText
{
    // Pass the text to the super class first
    [super setAttributedText:attributedText];

    [self.textStorage setAttributedString:attributedText];
}
- (CGPoint)_textOffsetForGlyphRange:(NSRange)glyphRange
{
   CGPoint textOffset = CGPointZero;

   CGRect textBounds = [self.layoutManager boundingRectForGlyphRange:glyphRange 
                                                     inTextContainer:self.textContainer];
   CGFloat paddingHeight = (self.bounds.size.height - textBounds.size.height) / 2.0f;
   if (paddingHeight > 0)
   {
       textOffset.y = paddingHeight;
   }

   return textOffset;
}

- (void)drawTextInRect:(CGRect)rect
{
   // Calculate the offset of the text in the view
   CGPoint textOffset;
   NSRange glyphRange = [self.layoutManager glyphRangeForTextContainer:self.textContainer];
   textOffset = [self _textOffsetForGlyphRange:glyphRange];

   // Drawing code
   [self.layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:textOffset];

   // for debugging the following 2 line should produce the same results
   [self.layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:textOffset];
   //[super drawTextInRect:rect];
}

Shamelessly taken from here. Incredibly work on the original author's part for working this all out.

Upvotes: 5

pableiros
pableiros

Reputation: 16032

For Swift 3 using TTTAttributedLabel

let title: NSString = "Fork me on GitHub!"

var attirutedDictionary = NSMutableDictionary(dictionary:attributedLabel.linkAttributes)

attirutedDictionary[NSForegroundColorAttributeName] = UIColor.red
attirutedDictionary[NSUnderlineStyleAttributeName] =  NSNumber(value: NSUnderlineStyle.styleNone.rawValue)

attributedLabel.attributedText = NSAttributedString(string: title as String)
attributedLabel.linkAttributes = attirutedDictionary as! [AnyHashable: Any]

let range = subtitleTitle.range(of: "me")
let url = URL(string: "http://github.com/mattt/")

attributedLabel.addLink(to: url, with: range)

Upvotes: 0

Dana Wheeler
Dana Wheeler

Reputation: 64

Swift 3 example for TTTAttributedLabel:

yourLabel.linkAttributes = [
    NSForegroundColorAttributeName: UIColor.green,  
    NSUnderlineStyleAttributeName: NSNumber(value: NSUnderlineStyle.styleNone.rawValue)
]
yourLabel.activeLinkAttributes = [
    NSForegroundColorAttributeName: UIColor.green,  
    NSUnderlineStyleAttributeName: NSNumber(value: NSUnderlineStyle.styleDouble.rawValue)
]

Upvotes: 0

OdieO
OdieO

Reputation: 7004

So I ended up using TTTAttributedLabel:

-(void)createLinkFromWord:(NSString*)word withColor:(UIColor*)color atRange:(NSRange)range{
    NSMutableAttributedString* newTextWithLinks = [self.label.attributedText mutableCopy];
    NSURL *url = [NSURL URLWithString:@"http://www.reddit.com"];
    self.label.linkAttributes = @{NSForegroundColorAttributeName: color, 
                                   NSUnderlineStyleAttributeName: @(NSUnderlineStyleNone)};
    [self.label addLinkToURL:url withRange:range];
}

I found that OHAttributedLabel actually does have methods to set links and declare colors and underline styles for those links. However, I wanted the links to be different colors based on a parameter. TTTAttributedLabel allows this by letting you set it's linkAttributes property for each link you create.

Upvotes: 62

kwahn
kwahn

Reputation: 2148

I am using TTTAttributedLabel. I wanted to change the color of the linked text, and keep it underlined. Pim's answer looked great, but didn't work for me. Here's what did work:

label.linkAttributes = @{ (id)kCTForegroundColorAttributeName: [UIColor magentaColor],
                           (id)kCTUnderlineStyleAttributeName : [NSNumber numberWithInt:NSUnderlineStyleSingle] };

Note: if you don't want the text underlined, then remove the kCTUnderlineStyleAttributeName key from the dictionary.

Upvotes: 23

Pim
Pim

Reputation: 2128

Here's is my improved version of Ramsel's already great answer. I believe it's much more readable, and I hope it will come to good use.

label.linkAttributes = @{ NSForegroundColorAttributeName: [UIColor whiteColor],
                          NSUnderlineStyleAttributeName: [NSNumber numberWithInt:NSUnderlineStyleSingle] };

Here's a list of other attibute names.

Upvotes: 22

Related Questions