开发者

UIButton's Title Label Word Wrap with Tail Truncation

开发者 https://www.devze.com 2023-04-04 06:17 出处:网络
I need to enable word wrapping and 开发者_如何学Pythontail truncation, at the same time, on a UIButton\'s titleLabel. Setting numberOfLines to something more than 0 doesn\'t work, the text stays on on

I need to enable word wrapping and 开发者_如何学Pythontail 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.


I solved it the same day I posted this question by putting a UIButton on top of a UILabel with numberOfLines set to 3. I had left this unaccepted to see if someone had a better idea, but apparently there's no other solution.


[self.costomButton.titleLabel setTextAlignment:UITextAlignmentLeft];
[self.costomButton.titleLabel setNumberOfLines:3];

Be sure you should set Alignment first ps: this only work when the system version is bigger than 5.0


Try setting the numberOfLines more than 2, and set height also accordingly.

    m_button = [UIButton buttonWithType:UIButtonTypeCustom];
[m_button setFrame:CGRectMake(isLandscape?20:10, 40, isLandscape?300:250, 40)];
m_button.titleLabel.font  = [UIFont fontWithName:@"HelveticaNeue" size:17];
[m_btnDiscoverPoint setTitle:@"Title" forState:UIControlStateNormal];
CGRect buttonFrame = [m_button frame];

if ([m_button.titleLabel.text length]>0) {
    CGSize suggestedSize = [m_button.titleLabel.text sizeWithFont:[UIFont fontWithName:@"HelveticaNeue" size:17] constrainedToSize:CGSizeMake(FLT_MAX,m_button.frame.size.height) lineBreakMode:UILineBreakModeWordWrap];

    if (suggestedSize.width >= self.view.frame.size.width) {
        suggestedSize.width = self.view.frame.size.width-10;
        suggestedSize.height=suggestedSize.height+20;
        m_button.titleLabel.numberOfLines=2;
    }
    else{
        m_button.titleLabel.numberOfLines=1;
    }

    buttonFrame.size.width = suggestedSize.width;

    [m_button setFrame:buttonFrame];
}
[m_button setBackgroundColor:[UIColor clearColor]];
[m_button addTarget:self action:@selector(btnClickAction) forControlEvents:UIControlEventTouchUpInside];


button.titleLabel.numberOfLines = 2;
button.titleLabel.lineBreakMode = UILineBreakModeWordWrap;
UIFont * theFont = [UIFont systemFontOfSize: 14]; // you set
CGSize textSize = [titleStr sizeWithAttributes:@{NSFontAttributeName: theFont}];
CGFloat theWidth = kScreenWidth-otherWidthYouSet;// I thought the button's frame is content driving ,and is limited 
CGFloat ratio = theWidth*heightYouSet/((textSize.width+4)*(textSize.height+6));// 4 , 6 , is made by experience . I think the textSize is taken one line text default by the system 
NSUInteger validNum = ratio * titleStr.length;

if(ratio<1){
    [button setTitle: [[titleStr substringToIndex: validNum] stringByAppendingString: @"..."] state: yourState];

}
else{
    [button setTitle: titleStr state: yourState];
}


Swift 5:

You can have multiple lines along with tail truncation by simply setting "numberOfLines = 2" and "lineBreakMode" to "truncate tail" to your UILabel.

UIButton's Title Label Word Wrap with Tail Truncation


All UI properties are deprecated in iOS Use NS abbreviations instead of UI. As shown example here - NSLineBreakByTruncatingMiddle


You can specify more than one lineBreakMode on a label by using the bitwise OR operator.

For example, the following code would wrap the text of the label, and would add the ellipsis on the tail end of the text when it expanded beyond the size of the label's frame height.

lblTemp.lineBreakMode = UILineBreakModeWordWrap | UILineBreakModeTailTruncation;
lblTemp.numberOfLines = 0;

UPDATE: this is not correct. It appears to work because UILineBreakModeWordWrap is 0 in the enum. See comments below.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号