压缩 UILabel 最后一行的文字

有个需求需要在 UILabel 的最后一行后面放个按钮,但是需要和 UILabel 内对齐,所以最后一行能显示的文字就需要进行压缩。

场景是文字最多出现三行,如果文字遮挡按钮(不管文字需要几行显示),遮挡的文字及前面两个字符就都进行压缩,变成…

初始思路是直接取最后一行宽度,然后计算多少个文字会挡,然后替换最后的几个文字。。

调研了几个方法,对英文来说有个比较常见的思路是,每次截取一段文本,以空格为分界线,然后不断往里面加词,直到超出一行的宽度,把这一行文本加入到一个数组中,然后在这行文本末尾进行切分。直到切完整段文本。
效率比较低,而且不适合中文。
参考链接:https://stackoverflow.com/questions/10193073/ios-determine-last-line-width-of-uilabel

采用的是这个链接里另一个方法,使用了 NSLayoutManager,计算出整个文本的每行及每个字符的位置,然后通过取最后一个字符的 Index,使用 lineFragmentRectForGlyphAtIndex:effectiveRange: 方法即可取出该字符所在行的 rect,宽度即在其中。

这个方法看起来有完整的 API 支持,决定从这个角度入手。

从另一个回答中看到,提问者有个类似的需求,不过他们是截取第二行的最后几个字符,不需要考虑只有一行的情况,所以可以用 Magic Number 直接指定 UILabel 中某个 point 的值,然后取出这个 point 的字符 Index,进行替换即可。

由于我们不是要取所有文本的最后一行,而是取三行内的最后一行,所以这个 Index 就无法简单的直接取出来了。

解决办法是使用 enumerateLineFragmentsForGlyphRange 来获取三行内最后一行的 rect,然后和需要截取的位置进行比较,如果会发生文字和按钮的重叠,就取重叠点的字符 Index,往前倒推两个Index,然后把剩下的文字替换…就行了。

代码:

- (NSAttributedString *)truncatingMessage:(NSAttributedString *)message
                                 forLabel:(UILabel *)label
                      truncatingTailWidth:(CGFloat)tailWidth {

    CGSize labelSize = CGSizeMake(label.width, INFINITY);
    NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
    NSTextContainer *container = [[NSTextContainer alloc] initWithSize:labelSize];
    NSTextStorage *storage = [[NSTextStorage alloc] initWithAttributedString:message];
    
    [layoutManager addTextContainer:container];
    [storage addLayoutManager:layoutManager];
    
    container.lineFragmentPadding = 0;
    container.lineBreakMode = label.lineBreakMode;
    container.maximumNumberOfLines = label.numberOfLines;
    
    NSRange range;
    [layoutManager glyphRangeForCharacterRange:NSMakeRange(0, message.length - 1) actualCharacterRange:&range];
    
    __block NSUInteger i = 0;
    __block CGRect lastUsedRect;
    __block NSRange lastGlyphRange;
    
    [layoutManager enumerateLineFragmentsForGlyphRange:range usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer * _Nonnull textContainer, NSRange glyphRange, BOOL * _Nonnull stop) {
        if (i < container.maximumNumberOfLines) {
            lastUsedRect = usedRect;
            lastGlyphRange = glyphRange;
            i += 1;
        } else {
            *stop = YES;
        }
    }];
    
    NSAttributedString *stringToUse = message;
    
    // 最后一行文字宽度会与按钮重叠才需要处理
    if (lastUsedRect.size.width > label.width - tailWidth) {
        // 算出重叠位置
        CGPoint ellipsisPoint = CGPointMake(label.width - tailWidth, lastUsedRect.origin.y + lastUsedRect.size.height / 2);
        // 重叠位置的字符 Index
        NSUInteger characterIndex = [layoutManager characterIndexForPoint:ellipsisPoint inTextContainer:container fractionOfDistanceBetweenInsertionPoints:nil];
        // 如果往后退两个字符就到了上一行,就不进行压缩了, 这里的 location 指的是最后一行第一个字符的 Index
        if (characterIndex - 2 > lastGlyphRange.location) {
            NSMutableAttributedString *tempString = [[message attributedSubstringFromRange:NSMakeRange(0, characterIndex - 2)] mutableCopy];
            NSDictionary *attributes = [message attributesAtIndex:0 effectiveRange:nil];
            [tempString appendAttributedString:[[NSAttributedString alloc] initWithString:@"…" attributes:attributes]];
            stringToUse = [tempString copy];
        }
    }
    
    return stringToUse;
}

你可能感兴趣的:(压缩 UILabel 最后一行的文字)