NSLineBreakByTruncatingTail on UITextView removing new line breaks - ios

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

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;

Indent second line of UILabel

So I have a UILabel that may or may not go to a second line, depending if it is on iPhone or iPad. What I would like to accomplish is to have it indent on the second line to line up correctly, if needed.
On iPad it will almost never need the second line break, and depending on which iPhone it is running on, it may or may not. So, in essence, I need a way to dynamically indent the second line, only when there is a second line.
Use an NSAttributedString for your label, and set the headIndent of its paragraph style:
NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
style.headIndent = 14;
NSDictionary *attributes = #{
NSParagraphStyleAttributeName: style
};
NSAttributedString *richText = [[NSAttributedString alloc] initWithString:#"So this UILabel walks into a bar…" attributes:attributes];
self.narrowLabel.attributedText = richText;
self.wideLabel.attributedText = richText;
Result:

iOS Swift Wrapping Text Around Image

I have a UILabel that will contain various lengths of text. I need to place an image in the upper left corner of the text and have the text wrap around it. How can I do this? All I could find was using a UITextView which I don't want to use since it's static text.
This is a perfectly reasonable use of a UITextView. Your reasons for hesitation to use it are unclear. You can make the UITextView non-editable and non-selectable; the user will not know that it is a UITextView as opposed to to a UILabel.
If you don't like that solution, then what I would do is use, instead of a UILabel, a custom view that draws the text. You can draw the text with Text Kit and thus you can take complete charge of how the text draws. In particular, you can cause it to wrap however you like, including not drawing the text in the corner (exclusion path on the text container).
You can achieve this using NSTextAttachment and attributed text.
NSMutableAttributedString *myText = [[NSMutableAttributedString alloc] initWithString:labelStr];
NSTextAttachment *attachment = [[NSTextAttachment alloc] init]
attachment.image = yourImage;
NSAttributedString *attachmentLock = [NSAttributedString attributedStringWithAttachment:attachment];
NSMutableAttributedString *lockString = [[NSMutableAttributedString alloc] initWithAttributedString:myText];
//set your image range within the text. modify it till you get it right.
NSRange range = NSMakeRange(0,[labelStr length]);
[lockString replaceCharactersInRange:NSMakeRange(range.location, 1) withAttributedString:attachmentLock];
yourLabel.attributedText = lockString;

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];

UILabel attributedText with multiple line break modes

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.

Resources