一、前言

继上次本文本有行间距,当时交互有另一个需求,需要在文本最后一行省略符号放中间,只省略到最后一行的中间。如下图需求。Label的自带LineBreakMode不支持如下的设置。便要自己处理,经过网上的资料参考和同事J学习探讨,这里记录一下解决方案与过程。

图1.png

二、分析

文本只需要最后一行进行处理,因此取得能取得文本最后一行,并进行计算,当最后一行的文本超过中间,则截取字符到中间,并增加一个“…”字符串。

三、解决方案

核心方法为网上大神所写获得Label每行文本字符串数组的方法,对拿到的最后一行进行处理。处理方式还是要利用获取每行文本的方法,传入一个显示label宽度的一半label。这时计算出来的最后一行的点点点省略号,误差就在一个字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//获得Label每行的文本字符串数组
- (NSArray *)getLinesArrayOfStringInLabel:(UILabel *)label{
    NSString *text = [label text];
    UIFont *font = [label font];
    CGRect rect = [label bounds];
    CTFontRef myFont = CTFontCreateWithName((__bridge CFStringRef)([font fontName]), [font pointSize], NULL);
    NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:text];
    [attStr addAttribute:(NSString *)kCTFontAttributeName value:(__bridge id)myFont range:NSMakeRange(0, attStr.length)];
    CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)attStr);
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, CGRectMake(0,0,rect.size.width,100000));
    CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, NULL);
    NSArray *lines = (__bridge NSArray *)CTFrameGetLines(frame);
    NSMutableArray *linesArray = [[NSMutableArray alloc]init];
    for (id line in lines){
        CTLineRef lineRef = (__bridge CTLineRef )line;
        CFRange lineRange = CTLineGetStringRange(lineRef);
        NSRange range = NSMakeRange(lineRange.location, lineRange.length);
        NSString *lineString = [text substringWithRange:range];
        [linesArray addObject:lineString];
    }
    return (NSArray *)linesArray;
}

再利用此方法处理最后一行文本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//Label每行文本数组
 NSArray *separatedLines = [NSString getSeparatedLinesFromLabel:self.label];
    
    NSMutableString *limitedText = [NSMutableString string];
    if ( separatedLines.count >= self.label.numberOfLines ) {//当超过最大行数
        for (int i = 0 ; i < self.label.numberOfLines; i++) {
            if ( i == self.label.numberOfLines - 1) {//处理最后一行文本
                UILabel *lastLineLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, self.label.frame.size.width/2, MAXFLOAT)];
                lastLineLabel.text = separatedLines[self.label.numberOfLines - 1];
                NSArray *subSeparatedLines = [NSString getSeparatedLinesFromLabel:lastLineLabel];
                NSString *lastLineText = [subSeparatedLines firstObject];
                NSInteger lastLineTextCount = lastLineText.length;
                [limitedText appendString:[NSString stringWithFormat:@"%@...",[lastLineText substringToIndex:lastLineTextCount]]];
            }else{//非最后一行,则将文本进行存储
                [limitedText appendString:separatedLines[i]];
            }
        }  
    }else{
        [limitedText appendString:self.text];
    }
    
self.label.text = limitedText;

四、封装与使用

写一个Label分类,对需要进行最后一行中间省略号的Label调用一下 setLineBreakByTruncatingLastLineMiddle 方法,同时需要设置一下最大行数numberOfLines。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#import "UILabel+QT.h"
#import <CoreText/CoreText.h>
@implementation UILabel (QT)
- (void)setLineBreakByTruncatingLastLineMiddle{
    if ( self.numberOfLines <= 0 ) {
        return;
    }
    NSArray *separatedLines = [self getSeparatedLinesArray];
    
    NSMutableString *limitedText = [NSMutableString string];
    if ( separatedLines.count >= self.numberOfLines ) {
        
        for (int i = 0 ; i < self.numberOfLines; i++) {
            if ( i == self.numberOfLines - 1) {
                UILabel *lastLineLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, self.frame.size.width/2, MAXFLOAT)];
                lastLineLabel.text = separatedLines[self.numberOfLines - 1];
                
                NSArray *subSeparatedLines = [lastLineLabel getSeparatedLinesArray];
                NSString *lastLineText = [subSeparatedLines firstObject];
                NSInteger lastLineTextCount = lastLineText.length;
                [limitedText appendString:[NSString stringWithFormat:@"%@...",[lastLineText substringToIndex:lastLineTextCount]]];
            }else{
                [limitedText appendString:separatedLines[i]];
            }
        }
        
        
    }else{
        [limitedText appendString:self.text];
    }
    
    self.text = limitedText;
    
}
- (NSArray *)getSeparatedLinesArray
{
    NSString *text = [self text];
    UIFont   *font = [self font];
    CGRect    rect = [self frame];
    
    CTFontRef myFont = CTFontCreateWithName((__bridge CFStringRef)([font fontName]), [font pointSize], NULL);
    NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:text];
    [attStr addAttribute:(NSString *)kCTFontAttributeName value:(__bridge id)myFont range:NSMakeRange(0, attStr.length)];
    
    CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)attStr);
    
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, CGRectMake(0,0,rect.size.width,100000));
    
    CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, NULL);
    
    NSArray *lines = (__bridge NSArray *)CTFrameGetLines(frame);
    NSMutableArray *linesArray = [[NSMutableArray alloc]init];
    
    for (id line in lines)
    {
        CTLineRef lineRef = (__bridge CTLineRef )line;
        CFRange lineRange = CTLineGetStringRange(lineRef);
        NSRange range = NSMakeRange(lineRange.location, lineRange.length);
        
        NSString *lineString = [text substringWithRange:range];
        [linesArray addObject:lineString];
    }
    return (NSArray *)linesArray;
}
@end

五、思考

系统可以直接Label的lineBreakMode,如果可以给系统lineBreakMode增加一个枚举类型NSLineBreakByTruncatingLastLineMiddle,那在使用的时候,直接设置一下就好了是该多方便。不知道这个想法的可行性,需要学习了解看看。如果后续有这样的解决方案,再来补充。

经过探讨,想给系统lineBreakMode增加一个枚举还是行不通的,毕竟系统的代码没有开源,其次,要是能修改也是自己能用。所以,遇到这种情况,可以写一个类方法,或者是给类增加一个属性,例如otherLineBreakMode,进行处理。

六、参考资料

http://stackoverflow.com/questions/34867231/issue-get-lines-array-of-string-inn-label