I need to enable word wrapping and tail truncation, at the same time, on a UIButton
's titleLabel
. Setting numberOfLines to something more than 0 doesn't work, the text stays on one line.
I've already searched around and haven't found a solution. Any idea?
This is not correct:
lblTemp.lineBreakMode = NSLineBreakByWordWrapping | NSLineBreakByTruncatingTail
lblTemp.numberOfLines = 0;
NSLineBreakMode is defined in NSParagraphStyle.h as:
typedef NS_ENUM(NSInteger, NSLineBreakMode) { /* What to do with long lines */
NSLineBreakByWordWrapping = 0, /* Wrap at word boundaries, default */
NSLineBreakByCharWrapping, /* Wrap at character boundaries */
NSLineBreakByClipping, /* Simply clip */
NSLineBreakByTruncatingHead, /* Truncate at head of line: "...wxyz" */
NSLineBreakByTruncatingTail, /* Truncate at tail of line: "abcd..." */
NSLineBreakByTruncatingMiddle /* Truncate middle of line: "ab...yz" */
} NS_ENUM_AVAILABLE_IOS(6_0);
Note that it is an NS_ENUM, not an NS_OPTION, so it is not meant to be used as a mask. For more information see this.
In reality using the | operator on those constants leads to a mask matching NSLineBreakByTruncatingTail:
(NSLineBreakByWordWrapping | NSLineBreakByTruncatingTail) == 4
NSLineBreakByTruncatingTail == 4
As far as I know, truncating the last line in Core Text and also doing word wrapping can not be done with the simple CTFramesetterCreateWithAttributedString & CTFrameDraw APIs, but can be done with line by line layout, which UILabel must be doing.
iOS 6 simplifies this by exposing new drawing APIs in NSStringDrawing.h:
typedef NS_ENUM(NSInteger, NSStringDrawingOptions) {
NSStringDrawingTruncatesLastVisibleLine = 1 << 5, // Truncates and adds the ellipsis character to the last visible line if the text doesn't fit into the bounds specified. Ignored if NSStringDrawingUsesLineFragmentOrigin is not also set.
NSStringDrawingUsesLineFragmentOrigin = 1 << 0, // The specified origin is the line fragment origin, not the base line origin
NSStringDrawingUsesFontLeading = 1 << 1, // Uses the font leading for calculating line heights
NSStringDrawingUsesDeviceMetrics = 1 << 3, // Uses image glyph bounds instead of typographic bounds
} NS_ENUM_AVAILABLE_IOS(6_0);
@interface NSAttributedString (NSExtendedStringDrawing)
- (void)drawWithRect:(CGRect)rect options:(NSStringDrawingOptions)options context:(NSStringDrawingContext *)context NS_AVAILABLE_IOS(6_0);
- (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options context:(NSStringDrawingContext *)context NS_AVAILABLE_IOS(6_0);
@end
So if you are using UILabel, you want your NSAttributedString's NSParagraphStyle or the lineBreakMode on the label itself to be set to :
NSLineBreakByTruncatingTail
And the numberOfLines property on the label must be set to 0.
From the UILabel headers on numberOfLines:
// if the height of the text reaches the # of lines or the height of the view is less than the # of lines allowed, the text will be
// truncated using the line break mode.
From the UILabel documentation:
This property controls the maximum number of lines to use in order to fit the label’s text into its bounding rectangle. The default value for this property is 1. To remove any maximum limit, and use as many lines as needed, set the value of this property to 0.
If you constrain your text using this property, any text that does not fit within the maximum number of lines and inside the bounding rectangle of the label is truncated using the appropriate line break mode.
The only problem that arises with this somewhat obscure feature of UILabel is that you can not get the size before drawing (which is a necessity for some UITableView + UITableViewCell dynamic layouts) without resorting to modifying the NSAttributedString's NSParagraphStyle on the fly.
As of iOS 6.1.4, calling -boundingRectWithSize:options:context with a NSAttributedString that has a NSLineBreakByTruncatingTail line break mode (for UILabel), returns an incorrect single line height even if the following options are passed in:
(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine)
(Please note that NSStringDrawingUsesLineFragmentOrigin is a necessity for multi line strings.)
What is worse is that UILabel's lineBreakMode does not override the NSAttributedStrings paragraph style, so you have to modify your attributed string's paragraph style for your sizing calculation, and later for passing it to the UILabel so it can draw it.
That is, NSLineBreakByWordWrapping for -boundingRectWithSize:options:context and NSLineBreakByTruncatingTail for the UILabel (so it can, use NSStringDrawingTruncatesLastVisibleLine internally, or whatever it does to clip the last line)
The only alternative if you do not want to mutate your string's paragraph style more than once much would be to do a simple UIView subclass that overrides -drawRect: (with the appropriate contentMode set to redraw as well), and uses iOS 6's new drawing API:
- (void)drawWithRect:(CGRect)rect options:(NSStringDrawingOptions)options context:(NSStringDrawingContext *)context NS_AVAILABLE_IOS(6_0);
Remembering to use NSLineBreakByWordWrapping and passing in (NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine) as the options.
Finally, before iOS 6, if you wanted to do word wrapping + tail truncation for an attributed string you would have to do line by line layout yourself with Core Text.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With