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

前端 未结 11 1129
终归单人心
终归单人心 2020-12-24 05:18

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 lin

相关标签:
11条回答
  • 2020-12-24 05:42

    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.

    0 讨论(0)
  • 2020-12-24 05:43

    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.

    0 讨论(0)
  • 2020-12-24 05:43

    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)
    ]
    
    0 讨论(0)
  • 2020-12-24 05:45

    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.

    0 讨论(0)
  • 2020-12-24 05:46

    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

    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

    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)
    
    0 讨论(0)
  • 2020-12-24 05:50

    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.

    0 讨论(0)
提交回复
热议问题