在特定位置截断 UILabel

Posted

技术标签:

【中文标题】在特定位置截断 UILabel【英文标题】:Truncate UILabel in specific location 【发布时间】:2014-07-16 14:17:25 【问题描述】:

我使用表格视图来显示图书列表,其中每个单元格都有一个 UILabel 显示书名,另一个 UILabel 显示图书作者

我的问题是关于作者标签的。一本书可以有多个作者,我希望它的行为如下:

如果图书有一位作者(“John Colman”),标签应为:“John Colman” 如果图书有多个作者(“John Colman”、“Bob Night”、“Michael”),标签应为:“John Colman +2 authors”

现在的问题是,我希望在“+”之前截断标签。例如,如果第一作者的名字很长,比如说“Benjamin Walter Jackson”,我希望标签看起来像这样:

"Benjamin Walter Ja... +2 authors"

默认行为当然会在最后截断标签,所以看起来像这样:

"Benjamin Walter Jackson +2 au..."  

如果我使用中间截断,则无法保证它会在正确的位置(在“+”之前)截断标签

我正在寻找一种方法来做到这一点,并且尽可能高效,而不影响表格视图的滚动性能。

【问题讨论】:

嗯,也许最简单的方法是使用 2 个标签。一个用于名称,另一个用于 +2 ...。因此,如果您的名称不合适,它将在最后得到 ... 如果合适,则将第一个标签的大小调整为字符串大小并移动第二个标签在第一个结束时。如果您尝试不同的方法,我会看到很多计算;) 【参考方案1】:

编辑:将解决方案推广到使用任何“截断位置”字符串。以前的版本仅在字符串 @" +" 的实例处被截断。编辑允许您定义您希望截断发生的位置。


我从this question(这是根据this site 上的答案修改的答案)得到我的答案,并根据您的需要对其进行了定制。创建一个新的NSString 接口,您可以在其中发送要自定义截断的字符串。

注意:此解决方案仅适用于 ios 7+。要在 iOS 6 中使用,请在 NSString+TruncateToWidth.m 文件中使用 sizeWithFont: 而不是 sizeWithAttributes:

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

使用它:

// 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

【讨论】:

感谢您的回复,您的解决方案我只有一个问题,如果作者姓名包含 '+' 怎么办?字符串将比提前截断 我刚刚注意到您在评论中提到了这一点,有什么想法可以解决这个问题吗? 没关系通过 [truncatedString rangeOfString:@" +" options:NSBackwardsSearch]; while sn-p 应该还有一个条件:range.location &gt;= 0 @kientux 虽然这会使它使用起来更安全,但它最终也会双重截断你的字符串,因为这意味着在传递的字符串之前删除所有内容对于标签来说仍然太宽。您最终会得到上面示例中的“... + 2 auth...”。只是要记住的事情。【参考方案2】:

UILabel 在 iOS 7 下无法处理这样的截断功能。要么你应该自己根据固定的(用 uilabel 字体大小计算)长度截断字符串,但这很头疼,或者你可以使用两个 UILabel。第一作者固定大小的第一个 uilabel。如您所知,它会在最后自动截断。那么第二个 uilabel 应该准确地绘制在第一个标签的右侧,带有“+ 2 个其他作者”。

编辑: 看看@Stonz2 的回答

【讨论】:

【参考方案3】:

此答案基于https://***.com/a/30813691/2123122。这是示例代码。

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

现在您可以按如下方式使用它:

 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;
 

我创建了一个名为 ResponsiveLabel 的 UILabel 子类,它处理自定义截断标记以及将样式应用于用户句柄、URL、主题标签等模式。

【讨论】:

以上是关于在特定位置截断 UILabel的主要内容,如果未能解决你的问题,请参考以下文章

TSQL 字符串函数:截断和查找

sql 截断特定模式中的所有表。

使用 Mongodb 在 Spring 中截断和计算位置

确定 UITextView 将截断字符串的位置

Keras model.to_json() 错误:“rawunicodeescape”编解码器无法解码位置 94-98 中的字节:截断 \uXXXX

审计截断和丢弃