简体   繁体   中英

Truncate UILabel in specific location

I use a table view to show a list of books, where each cell has a UILabel that shows the book's name and another UILabel the shows the book's author(s)

My question is about the author(s) label. A book can have multiple authors, and I want it to behave as follows:

  • If book has one author ('John Colman') label should be: "John Colman"
  • If book has more than one author ('John Colman', 'Bob Night', 'Michael') label should be: "John Colman +2 authors"

Now the problem is this, I want the label to be truncated before the '+'. So for example, if the first author name is long, lets say 'Benjamin Walter Jackson', I want the label to look like this:

"Benjamin Walter Ja... +2 authors"

The default behaviour of course truncates the label in the end, so it looks like this:

"Benjamin Walter Jackson +2 au..."  

If I use the middle truncate, there's no promise that it will truncate the label in the right place (before the '+')

I'm looking for a way to do it and as efficient as possible, without impacting the scroll performance of the table view.

Edit: Generalized the solution to work with any "truncation location" string. Previous version only truncated at instance of string @" +" . Edit allows you to define where you want the truncation to happen.


I took my answer from this question (which was an answer modified from the answer on this site ) and tailored it to fit your needs. Create a new NSString interface where you can send your string to be custom-truncated.

NOTE: This solution is for iOS 7+ only. To use in iOS 6, use sizeWithFont: instead of sizeWithAttributes: in the NSString+TruncateToWidth.m file.

NSString+TruncateToWidth.h

@interface NSString (TruncateToWidth)
- (NSString*)stringByTruncatingAtString:(NSString *)string toWidth:(CGFloat)width withFont:(UIFont *)font;
@end

NSString+TruncateToWidth.m

#import "NSString+TruncateToWidth.h"

#define ellipsis @"…"

@implementation NSString (TruncateToWidth)

- (NSString*)stringByTruncatingAtString:(NSString *)string toWidth:(CGFloat)width withFont:(UIFont *)font
{
    // If the string is already short enough, or 
    // if the 'truncation location' string doesn't exist
    // go ahead and pass the string back unmodified.
    if ([self sizeWithAttributes:@{NSFontAttributeName:font}].width < width ||
        [self rangeOfString:string].location == NSNotFound)
        return self;

    // Create copy that will be the returned result
    NSMutableString *truncatedString = [self mutableCopy];

    // Accommodate for ellipsis we'll tack on the beginning
    width -= [ellipsis sizeWithAttributes:@{NSFontAttributeName:font}].width;

    // Get range of the passed string. Note that this only works to the first instance found,
    // so if there are multiple, you need to modify your solution
    NSRange range = [truncatedString rangeOfString:string];
    range.length = 1;

    while([truncatedString sizeWithAttributes:@{NSFontAttributeName:font}].width > width 
           && range.location > 0)
    {
        range.location -= 1;
        [truncatedString deleteCharactersInRange:range];
    }

    // Append ellipsis
    range.length = 0;
    [truncatedString replaceCharactersInRange:range withString:ellipsis];

    return truncatedString;
}

@end

Using it:

// Make sure to import the header file where you want to use it
myLabel.text = [@"Benjamin Walker Jackson + 2 authors" stringByTruncatingAtString:@" +" toWidth:myLabel.frame.size.width withFont:myLabel.font];
// Sample Result: Benjamin Walte... + 2 authors

UILabel cannot handle such truncate function other than under iOS 7. either you should truncate the string according to the fixed(calculated with uilabel font size) length yourself but that's a headache, or you can use two UILabels. 1st uilabel with fixed size for first author. it will auto truncate at the end as you know already. then the 2nd uilabel should be drawn exactly on the righ side of first label with of " + 2 other authors".

Edit: take a look at @Stonz2 answer

This answer is based on https://stackoverflow.com/a/30813691/2123122 . Here is the sample code.

@interface CustomLabel()

  @property (nonatomic, retain) NSLayoutManager *layoutManager;
  @property (nonatomic, retain) NSTextContainer *textContainer;
  @property (nonatomic, retain) NSTextStorage *textStorage;

@end

@implementation CustomLabel

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
       self = [super initWithCoder:aDecoder];
       if (self) {
         [self configureTextkitStack];
       }
       return self;
 }
 - (instancetype)initWithFrame:(CGRect)frame {
       self = [super initWithFrame:frame];
       if (self) {
         [self configureTextkitStack];
       }
       return self;
  }

- (void)configureTextkitStack {
      _textContainer = [[NSTextContainer alloc] init];
      _textContainer.lineFragmentPadding = 0;
      _textContainer.maximumNumberOfLines = self.numberOfLines;
      _textContainer.lineBreakMode = self.lineBreakMode;
      _textContainer.widthTracksTextView = YES;

      _layoutManager = [[NSLayoutManager alloc] init];
      [_layoutManager addTextContainer:self.textContainer];

      [_textContainer setLayoutManager:self.layoutManager];

      _textStorage = [[NSTextStorage alloc] init];
      [_textStorage addLayoutManager:self.layoutManager];
      [self.layoutManager setTextStorage:_textStorage];
}

- (NSRange)rangeForTokenInsertion {
      self.textContainer.size = self.bounds.size;
      if (self.attributedText.length > 0 ){
        [self.textStorage setAttributedString:[[NSMutableAttributedString alloc]initWithAttributedString:self.attributedText]];
      }
      if (self.text.length == 0) {
        return NSMakeRange(NSNotFound, 0);
      }

      NSInteger glyphIndex = [self.layoutManager glyphIndexForCharacterAtIndex:self.textStorage.length - 1];
      NSRange range = [self.layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:glyphIndex];

      return range;
    }

Now you can use this as follows:

 NSRange range = [self.label rangeForTokenInsertion];
 NSString *token = @"...+2 authors";
 if (range.location != NSNotFound ) {
  range.length += token.length;
  range.location -= token.length;
 }
 if (range.location != NSNotFound) {
  NSString *finalString = [self.label.text  stringByReplacingCharactersInRange:range withString:token];
  self.label.text = finalString;
 }

I have created a UILabel subclass called ResponsiveLabel which handles custom truncation token as well as applying styles to patterns like userhandles, URLs, hashtags etc.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM