美文网首页
压缩 UILabel 最后一行的文字

压缩 UILabel 最后一行的文字

作者: wiilen | 来源:发表于2019-06-15 12:15 被阅读0次

    有个需求需要在 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 最后一行的文字

          本文链接:https://www.haomeiwen.com/subject/agfbfctx.html