NSAttributedString on multiline UILabel cutting off first line

I've looked at ~10 questions on SO and am still coming up short.

I have a multiline UILabel (created in Interface Builder, numberOfLines set to 0) which renders normally like this:

I want to be underline "Terms of Service" and "Privacy Policy", so I added this code:

NSString *text = self.agreement.text;
NSMutableAttributedString *aString = [[NSMutableAttributedString alloc] initWithString:text];

NSRange privacyRange = [text rangeOfString:@"privacy policy" options:NSCaseInsensitiveSearch];
[aString addAttribute:NSUnderlineStyleAttributeName value:@(NSUnderlineStyleSingle) range:privacyRange];

NSRange tosRange = [text rangeOfString:@"terms of service" options:NSCaseInsensitiveSearch];
[aString addAttribute:NSUnderlineStyleAttributeName value:@(NSUnderlineStyleSingle) range:tosRange];

self.agreement.attributedText = aString;

But the result looks like this:

What do I need to do so both lines appear, with the appropriate ranges underlined?

Also, I'd prefer not to use a 3rd Party Library like OHAttributedLabel or TTTAttributedLabel since this is the only place in my app where I need to underline a piece of text.

What I've Tried

  • calling sizeToFit after setting the attributed text
  • using a UITextView instead. Both lines rendered correctly but I lost the center alignment.
  • resetting numberOfLines and lineBreakMode in code

Sascha asked me to upload two screenshots with the background colors set to something other than clear. Oddly enough, everything shows up as expected! Not sure what to make of this or what this is telling us.

Answers


  1. First create attributed text as you did:

    NSString *text = self.agreement.text;
    NSMutableAttributedString *aString = [[NSMutableAttributedString alloc] initWithString:text];
    
    NSRange privacyRange = [text rangeOfString:@"privacy policy" options:NSCaseInsensitiveSearch];
    [aString addAttribute:NSUnderlineStyleAttributeName value:@(NSUnderlineStyleSingle) range:privacyRange];
    
    NSRange tosRange = [text rangeOfString:@"terms of service" options:NSCaseInsensitiveSearch];
    [aString addAttribute:NSUnderlineStyleAttributeName value:@(NSUnderlineStyleSingle) range:tosRange];
    
  2. Assign font you'd like to use to your attributed string:

    [aString addAttribute:NSFontAttributeName value:self.agreement.font range:(NSRange){0, aString.length}];
    
    self.agreement.attributedText = aString;
    
  3. Define your label to be multiline:

    self.agreement.numberOfLines = 0;
    self.agreement.lineBreakMode = NSLineBreakByWordWrapping;
    
  4. Estimate your label size:

    CGRect myLabelRect = [aString boundingRectWithSize:CGSizeMake(self.view.frame.size.width, INT_MAX)
                                               options:NSStringDrawingUsesLineFragmentOrigin
                                               context:nil];
    
  5. Use estimated size when adding label to view:

    self.agreement.frame = CGRectMake(self.view.frame.size.width / 2 - myLabelRect.size.width / 2,
                                      0,
                                      myLabelRect.size.width,
                                      myLabelRect.size.height);
    [self.view addSubview:self.agreement];
    

So yesterday, based on tsafrir's comments (iOS 7 bug, should be fixed in 7.1), I broke the two lines into two labels and moved on.

Then, SashaHameister recommended I see what happens when I apply a non-clear background-color. So I deleted the top label, added the text to the second label so it was two lines, change the background-color, and both lines rendered fine. I thought that was the solution (even though it wouldn't work because I needed a clear background).

After that, I tested changing it back to clear, and it still worked for me. It turns out it had something to do with the size of my label. My two line label had a height of ~75, way more than necessary for a two line label at 11pt font. Because of the adjustments I made yesterday (breaking the lines up to two labels), when I copy and pasted the first label's text into the second, the height only ended up being 48px, and that renders just fine.

I still have no idea why this makes such an impact, but if you're having the same problem, chances are your label's height is larger than it needs to be. Try reducing it as much as possible and get it as tight on the actual text as you can.


Need Your Help

Bringing UITextField to the top in UITableViewCell

objective-c cocoa-touch ios uitableview uitextfield

I have UITextField in a UITableViewCell. When I click on it, the keyboard obscures it. I have tries but it does not work:

How do you create a class that does not inherit from Object.prototype using ES6 classes?

javascript class null prototype ecmascript-6

I can create a class that does not inherit from Object.prototype using the older syntax.