iOS UIlabel 分行显示的实现思路

188 阅读3分钟

先上一张图需求大概是这样的

image.png

  1. 简单描述下我的大概需求 在列表数据中标题分为两行展示 数量与标题底部对齐 数据距右边间距是固定的 标题可能存在只有一行的情况。

以此为背景简述实现的过程

两种实现方案

先说说第一种 1:

核心代码Step1

// 先拿到UIlabel 显示的每一行的数据  
- (NSArray *)getLinesArrayOfStringWidth:(CGFloat)width {

    NSString *text = [self text];
    UIFont *font = [self font];
    if (text == nil) {
        return nil;
    }
    CTFontRef myFont = CTFontCreateWithName(( CFStringRef)([font fontName]), [font pointSize], NULL);
    NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:text];
    NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
    paragraphStyle.lineBreakMode = NSLineBreakByCharWrapping;
    [attStr addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, attStr.length)];
    [attStr addAttribute:(NSString *)kCTFontAttributeName
                   value:(__bridge  id)myFont
                   range:NSMakeRange(0, attStr.length)];
    CFRelease(myFont);
    CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString(( CFAttributedStringRef)attStr);
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, CGRectMake(0,0,width,100000));
    CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, NULL);
    NSArray *lines = ( 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];
        CFAttributedStringSetAttribute((CFMutableAttributedStringRef)attStr,
                                       lineRange,
                                       kCTKernAttributeName,
                                       (CFTypeRef)([NSNumber numberWithFloat:0.0]));
        CFAttributedStringSetAttribute((CFMutableAttributedStringRef)attStr,
                                       lineRange,
                                       kCTKernAttributeName,
                                       (CFTypeRef)([NSNumber numberWithInt:0.0]));
        [linesArray addObject:lineString];
    }
    CGPathRelease(path);
    CFRelease(frame);
    CFRelease(frameSetter);
    return (NSArray *)linesArray;
}

// 再计算UILabel 总的行数
- (int)getLinesWithLabelWidth:(CGFloat)width {
    return (int)[self getLinesArrayOfStringWidth:width].count;
}

既然是分行去处理数据的 我这里显示了两个所以我就定义了三个UILabel label1 label2 label3

  1. label1 负责接收服务端返回的所有数据但不展示 我是直接设置Hdiden = YES 只为第一步中拿到所有每一行的数据 所以必须初始化 我这里是做了隐藏操作
  2. label2 label3 分别用于显示 第一行第二行的数据

好 关键点来了 直接铺代码

    NSArray *lineStr = [self.tLabel getLinesArrayOfStringWidth:tWs];
    NSString *lastStr = lineStr.lastObject;
    if (lines > 2){
        lastStr = lineStr[1];
    }
//    // 如果是一行就处理第一行的数据 如果有两行就处理第二行的数据 处理逻辑一样
//    // 数量的宽度预留 XH_Width(50)  标题显示的最大可用宽度就是 tWs - XH_Width(50)
    if (lines == 1){ // 只有一行
        // 单行 label2 隐藏 数量显示与第一行底部对齐
        self.botTlabel.hidden = YES;
        self.topTlabel.text = lineStr.firstObject;
        [self.numLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
            make.centerY.mas_equalTo(self.topTlabel);
            make.right.mas_equalTo(-XH_Width(24));
            make.height.mas_equalTo(XH_Height(35));
        }];
        [self.topTlabel mas_remakeConstraints:^(MASConstraintMaker *make) {
            make.top.mas_equalTo(self.imgICon);
            make.left.mas_equalTo(self.imgICon.mas_right).offset(XH_Width(16));
            make.right.mas_equalTo(-XH_Width(60));
            make.height.mas_equalTo(XH_Height(35));
        }];
    }else if (lines > 1){ // 可能是好多行
    
    // 超出一行就设置 数量与label2 底部对齐
        self.topTlabel.text = lineStr.firstObject;
        self.botTlabel.hidden = NO;
        self.botTlabel.text = lastStr;
        [self.topTlabel mas_remakeConstraints:^(MASConstraintMaker *make) {
            make.top.mas_equalTo(self.imgICon);
            make.left.mas_equalTo(self.imgICon.mas_right).offset(XH_Width(16));
            make.right.mas_equalTo(-XH_Width(24));
            make.height.mas_equalTo(XH_Height(35));
        }];
        [self.numLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
            make.centerY.mas_equalTo(self.botTlabel);
            make.right.mas_equalTo(-XH_Width(24));
            make.height.mas_equalTo(XH_Height(35));
        }];

    }

再看下第二种实现方式

- (void)judeCurrengLineShowingWithStr:(NSString *)str lines:(int)line firstStr:(NSString *)firstStr {
    // 这个是自己内部的计算labe 宽度的方案需要替换成你自己的  
    CGFloat strWs = [HRLabel getWidthWithTitle:[str append:@"..."] font:[UIFont fontOfSize_PingFangSC_Regular:28]];
    // 可显示最大总宽度
    CGFloat tWs = kScreenW - XH_Width(264) - XH_Width(60);
    NSString *goJudgeStr = @"";
    goJudgeStr = str;
    // 超出就截取
    if (strWs > tWs) {
        NSString *resultStr = @"";
        resultStr = [self creduceCurrentStrThenJudge:goJudgeStr];
        DLog(@"仓库列表..截取后用于展示的数据==%@",self.resultStr);
        if (line == 1){
            self.tLabel.text = [self.resultStr append:@"..."];
        }else {
          self.tLabel.text = [NSString stringWithFormat:@"%@%@",firstStr,[self.resultStr append:@"..."]];
        }
    }else { // 没有超出直接显示
       // str + ...  可以直接展示
        if (line == 1){
            self.tLabel.text = str;
        }else {
            self.tLabel.text = [NSString stringWithFormat:@"%@%@",firstStr,str];
        }
    }
}



// 如果字符串超出最大显示宽度了就反复反向截取 我这里一次只截取一个字符 截取后 截取字符串 + ... 再计算
- (NSString *)creduceCurrentStrThenJudge:(NSString *)str {
    // 可显示最大总宽度
    CGFloat tWs = kScreenW - XH_Width(264) - XH_Width(60);
    NSString *subStr = [str substringToIndex:str.length - 1];
    str = subStr;
    // 同样的label 计算方法需要替换成自己内部的方法 
    CGFloat strWs = [HRLabel getWidthWithTitle:str font:[UIFont fontOfSize_PingFangSC_Regular:28]];
    if (strWs > tWs) {
        [self creduceCurrentStrThenJudge:subStr];
    }else {
        self.resultStr = subStr;
        return [subStr substringToIndex:str.length - 1];
    }
    return subStr;
}

写在最后:

  1. 两种方法实现都依赖于首先拿到label 所有展示行中每一行单独的数据
  2. 第二种实现在纯字符显示的时候是没有问题的 但是如果字符串中包含了字母或者数字我这里计算宽度的方法是会出现偏差的可能是我的计算方式有问题。
  3. 起初接到这个需求的时候第一感觉是很蒙蔽的不知道咋计算的 但是当你拿到UILabel 分行数据的时候就感觉思路一下就打开了 ...