UILabel attributedText with multiple line break modes - ios

I have a requirement of showing a UILabel with text that has two different styles (different colours, parts of the text bolded). This is solved easily enough by using the attributedText-property.
My problem is that the text may or may not be longer than what I can fit in my label. When using plain text everything works the way I want it to. The text is word wrapped to fit the number of lines in the label and the tail is truncated if/when the text is longer than can be shown in the label.
When I switch to using attributedText I am only able to choose between tail truncation and word wrapping. If I want the tail truncated the label only renders a single line with the truncated tail (even though it could fit 10 lines). If I choose word wrapping then the tail is not truncated but the lines that cannot fit in the label are simply not shown.
My content string does not contain any line breaks, it is simply one long string.

I missed truncation when I set linespacing, but all I had to to was add linebreakmode to paragraphstyle
NSMutableParagraphStyle *paragrahStyle = [[NSMutableParagraphStyle alloc] init];
[paragrahStyle setLineSpacing:1.5];
[paragrahStyle setLineBreakMode:NSLineBreakByTruncatingTail];
NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:#"Long string that truncates"];
[attributedText addAttribute:NSParagraphStyleAttributeName value:paragrahStyle range:NSMakeRange(0, [attributedText length])];
self.label.attributedText = attributedText;

They only way I've been able to get this to work is to not set a paragraph style.

try this:
[_text drawWithRect:_textRect
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine
attributes:attributes
context:nil];

You can set up an NSParagraphStyle with any lineBreakMode you please, and apply it to the string using NSParagraphStyleAttributeName. I don't know if all of the values of NSLineBreakMode are supported, but I have no reason to believe they aren't.

Related

Xcode UILabel bug? Line spacing cropping text with a UILabel

I have some designs I'm following for an iOS project. The font used is Avenir with relatively tight line spacing.
Some of these labels will have dynamic text, so I can't just make the label's size larger since the size should be determined by the content.
By default line spacing for a UILabel ends up pretty large.
If I adjust the Line Height Multiple or the Max Height, the text along the top ends up cropped.
It should behave like this (Affinity Designer)...
Is there a way to handle this?
Thanks for your help!
This works for me. By adding
minimumLineHeight
let string = NSMutableAttributedString(string: venue.name)
let style = NSMutableParagraphStyle()
style.lineHeightMultiple = 0.68
style.minimumLineHeight = nameLabel.font.lineHeight
string.addAttribute(NSAttributedString.Key.paragraphStyle,
value: style,
range: NSMakeRange(0, venue.name.count))
nameLabel.attributedText = string
Unfortunately the UILabel has several quirks when it comes to vertical adjustments. A somewhat hacky solution is to move the baseline of the first line down as needed. Depending on if your string ends with a newline, and the amount of tightening you do, you might need to add one or two extra newlines also, otherwise the rendering engine will clip the last line.
The code snippet assumes that self.label already has an attributed string assigned to it, and that it has line separator character 0x2028 between the lines. This is usually true when entering multi-line text in IB.
// 0x2028 is the unicode line separator character
// Use \n instead if it is what you have
// or calculate the length of the first line in some other way
NSInteger lengthOfFirstLine = [self.label.text componentsSeparatedByString:#"\u2028"][0].length;
NSMutableAttributedString *s = [[NSMutableAttributedString alloc] initWithAttributedString:self.label.attributedText];
// Add two more blank lines so that the rendering engine doesn't clip the last line
[s appendAttributedString:[[NSAttributedString alloc] initWithString:#"\n\n"]];
// Move the baseline offset for the first line down
// the other lines will adjust to this
// 50 is a value you will have to find what looks best for you
[s addAttribute:NSBaselineOffsetAttributeName value:#(-50) range:NSMakeRange(0, lengthOfFirstLine)];
self.label.attributedText = s;

NSLineBreakByTruncatingTail on UITextView removing new line breaks

I have a UITextView with attributedText that has multiple new line breaks included in its attributed string.
NSMutableAttributedString *info = [[NSMutableAttributedString alloc] initWithString:#""];
NSAttributedString *title = [[NSAttributedString alloc] initWithString: [NSString stringWithFormat:#"%#\n", item.title] attributes:titleAttributes];
NSAttributedString *description = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:#"%#\n\n", description] attributes:descriptionAttributes]
[info appendAttributedString:bioItemTitle];
[info appendAttributedString:bioItemDescription];
textView.attributedText = info;
I've set the lineBreakMode of the textView's textContainer to NSLineBreakByTruncatingTail.
textView.textContainer.lineBreakMode = NSLineBreakByTruncatingTail;
The textView's textContainer also has a maximum number of lines.
textView.textContainer.maximumNumberOfLines = 8;
The problem arises when the 8th line of the textView is a new line, and not a line of characters. The textContainer truncates by removing the new line and replacing it with the next line of written characters.
How do I preserve the new line while still setting a lineBreakMode?
See screenshots
Try using UILabel instead of UITextView. It seems to play nicer in regard to truncating a new line.
Random things to try on the off chance you haven't already that aren't even solutions but maybe workarounds, and may not work anyway, but whatever:
Change the maximumNumberOfLines to 0 and rely on frames/autolayout to size the text view and its container, perhaps in combination with setting the text container's heightTracksTextView property to true
Insert horizontal whitespace or invisibles at the beginning of lines that otherwise only contain a newline #hacky

Scaling NSAttributedString with Size Classes in iOS

I'm using NSAttributedString to make a section of a UILabel bold, and I've been looking at how I might scale the font across different size classes.
The only way I can currently see to do this is to use the existing font size of the label when adding the attribute:
CGFloat fontSize = self.label.font.pointSize;
NSMutableAttributedString * string = [[NSMutableAttributedString alloc] initWithString:#"Abc Abc"];
[string addAttribute:NSFontAttributeName
value:[UIFont boldSystemFontOfSize:fontSize]
range:NSMakeRange(0,3)];
self.label.attributedText = string;
To clarify: label is a UILabel with a regular system font, and the attributed string is used to make the first 3 letters bold.
This works reasonably well, but couples the creation of the attributed string with the label. Is there a way to scale the attributed string without knowing the font size up front?
After discussion with #Larcerax, I found that I could use the Autoshrink property on my UILabel in Interface Builder to ensure the font scales down appropriately, which gives the (somewhat dirty) solution of using an obscenely large maximum font size which can be scaled down:
NSMutableAttributedString * string = [[NSMutableAttributedString alloc] initWithString:#"Abc Abc"];
[string addAttribute:NSFontAttributeName
value:[UIFont boldSystemFontOfSize:200.0]
range:NSMakeRange(0,3)];
self.label.attributedText = string;
This does require deciding upon a maximum size ahead of time, though.

NSAttributedString end of first line indent

I want to have the first line in an NSAttributedString for a UITextView indented from the right side on the first line.
So the firstLineHeadIndent in NSParagraphStyle will indent the first line from the left. I want to do the same thing but from the right in my UITextView.
Here's a screenshot of how I want the text to wrap.
The Setting Text Margins article from the Text System User Interface Layer Programming Guide has this figure:
As you can see, there's no built-in mechanism to have a first line tail indent.
However, NSTextContainer has a property exclusionPaths which represents parts of its rectangular area from which text should be excluded. So, you could add a path for the upper-right corner to prevent text from going there.
UIBezierPath* path = /* compute path for upper-right portion that you want to exclude */;
NSMutableArray* paths = [textView.textContainer.exclusionPaths mutableCopy];
[paths addObject:path];
textView.textContainer.exclusionPaths = paths;
I'd suggest to create 2 different NSParagraphStyle: one specific for the first line and the second one for the rest of the text.
//Creating first Line Paragraph Style
NSMutableParagraphStyle *firstLineStyle = [[NSMutableParagraphStyle alloc] init];
[firstLineStyle setFirstLineHeadIndent:10];
[firstLineStyle setTailIndent:200]; //Note that according to the doc, it's in point, and go from the origin text (left for most case) to the end, it's more a length that a "margin" (from right) that's why I put a "high value"
//Read there: https://developer.apple.com/library/ios/documentation/Cocoa/Reference/ApplicationKit/Classes/NSMutableParagraphStyle_Class/index.html#//apple_ref/occ/instp/NSMutableParagraphStyle/tailIndent
//Creating Rest of Text Paragraph Style
NSMutableParagraphStyle *restOfTextStyle = [[NSMutableParagraphStyle alloc] init];
[restOfTextStyle setAlignement:NSTextAlignmentJustified];
//Other settings if needed
//Creating the NSAttributedString
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:originalString];
[attributedString addAttribute:NSParagraphStyleAttributeName value:firstLineStyle range:rangeOfFirstLine];
[attributedString addAttribute:NSParagraphStyleAttributeName
value:restOfTextStyle
range:NSMakeRange(rangeOfFirstLine.location+rangeOfFirstLine.length,
[originalString length]-(rangeOfFirstLine.location+rangeOfFirstLine.length))];
//Setting the NSAttributedString to your UITextView
[yourTextView setAttributedText:attributedString];

What's the best way to add a left margin to an NSAttributedString?

I'm trying to add a left hand margin to an NSAttributedString so that when I concatenate it with another NSAS, there is a bit of space between the two box frames.
All I have so far is this:
NSMutableAttributedString *issn = [[NSMutableAttributedString alloc] initWithString:jm.issn attributes:nil];
NSRange range = NSMakeRange(0, [issn length]);
[issn addAttribute:NSFontAttributeName
value:[UIFont fontWithName:#"AvenirNext-Medium" size:8]
range:range];
NSMutableAttributedString *textLabel = [[NSMutableAttributedString alloc] initWithAttributedString:title];
[textLabel appendAttributedString:issn];
I want the margin on the left side of the second string.
Thanks!
Edit: image upload
Why not just use a tab character between the two strings?
You could do this by changing your first line to this:
NSMutableAttributedString *issn = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:#"\t%#", jm.issn] attributes:nil];
This should output something like like what you want. You may, however, want to add 2 \t characters instead of one because depending on the string length, it may not need a tab character to align it (for example, in that exact string you posted, it didn't add anything to my output).
1 tab with your string:
2 tabs with your string:
You can't. If you're concatenating attributed strings then there is no "margin" around a specific range in the final string. How would that work with multiple lines or text wrapping?
If you want clear space within an attributed string, use white space characters - spaces or tabs. You can define the position of tab stops using paragraph styles.
All you can do is, add the required spaces(or whitespace characters) before the source string and then add it to your NSMutableAttributedString.
NSString *newString = [NSString stringWithFormat:#" %#", jm.issn] <- Have given two spaces here.
Thanks

Resources