iOS | CoreText简单实现卷展Label视图

开发背景

  • 公司最近项目中有一个如下图的需求,在Github找了好久没有发现类似的Demo,于是思考了几天,成功实现了这种效果。
需求效果:点击`更多`文本跳转到其他页面
  • 然后我经过改造,实现了类似于可折叠的Label效果,并支持转屏自动绘制。如下图所示:
最终效果 - 竖屏
最终效果 - 横屏

实现过程

  • 主要思路是利用CoreText系统库进行的富文本绘制;

  • 在最后一行时,通过不断让index减1,然后获得subAttrText,再加上...更多文本进行计算文本所占的行数lines,直到行数为1行;

  • 最后进行drawAttrText的绘制,并回调返回计算后的totalHeight,让控制器更新heightConstraint约束;

1.drawRect
-(void)drawRect:(CGRect)rect{
    [super drawRect:rect];
    
    if (!_attributedText) {
        return;
    }
    
    [self drawTextWithCompletion:^(CGFloat height, NSAttributedString *drawAttributedText) {
        [self addSubview:self.contentView];
        self.contentView.frame = CGRectMake(0, 0, self.bounds.size.width, height);
        self.contentView.attributedText = drawAttributedText;
        // 回调
        _action ? _action(HYExpandableLabelActionDidLoad, @(height)) : nil;
    }];
}
2.指定行数绘制
  • CGPathRef 绘制区域
CGRect rect = CGRectMake(0, 0, self.bounds.size.width, .size.height);
CGPathRef path = CGPathCreateWithRect(rect, nil);
  • CTFrameRef
CTFramesetterRef setter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)_attributedText);
CTFrameRef ctFrame = CTFramesetterCreateFrame(setter, CFRangeMake(0, _attributedText.length), path, NULL);
  • CTLines
NSArray *lines = (NSArray*)CTFrameGetLines(ctFrame);
  • CTLine Origins 每一行的绘制原点
CGPoint ctOriginPoints[lines.count];
CTFrameGetLineOrigins(ctFrame, CFRangeMake(0, 0), ctOriginPoints);
  • 计算绘制文本drawAttrText和总高度
NSMutableAttributedString *drawAttributedText = [NSMutableAttributedString new];
    
    for (int i=0; i<lines.count; i++) {
        if (lines.count > _maximumLines && i == _maximumLines) {
            break;
        }
        CTLineRef line = (__bridge CTLineRef)lines[i];
        
        CGPoint lineOrigin = ctOriginPoints[i];
        
        CFRange range = CTLineGetStringRange(line);
        NSAttributedString *subAttr = [_attributedText attributedSubstringFromRange:NSMakeRange(range.location, range.length)];

        if (lines.count > _maximumLines && i == _maximumLines - 1) {
            // 最后一行特殊处理
        }
        else{
            [drawAttributedText appendAttributedString:subAttr];
            
            totalHeight += [self heightForCTLine:line];
        }
    }
  • 最后一行的处理
NSMutableAttributedString *drawAttr = [[NSMutableAttributedString alloc] initWithAttributedString:subAttr];

for (int j=0; j<drawAttr.length; j++) {
    NSMutableAttributedString *lastLineAttr = [[NSMutableAttributedString alloc] initWithAttributedString:[drawAttr attributedSubstringFromRange:NSMakeRange(0, drawAttr.length-j)]];
    
    [lastLineAttr appendAttributedString:self.clickAttributedText];
    
    NSInteger number = [self numberOfLinesForAttributtedText:lastLineAttr withOriginPoint:lineOrigin];
    // 当满足为一行时,break
    if (number == 1) {
        [drawAttributedText appendAttributedString:lastLineAttr];
        
        CTLineRef moreLine = CTLineCreateWithAttributedString((__bridge CFAttributedStringRef)self.clickAttributedText);
        CGSize moreSize = CTLineGetBoundsWithOptions(moreLine, 0).size;
        // 点击区域
        self.clickArea = CGRectMake(self.bounds.size.width-moreSize.width, totalHeight, moreSize.width, moreSize.height);
        
        totalHeight += [self heightForCTLine:line];
        break;
    }
}
  • 回调
completion(totalHeight, drawAttributedText);
3.全部文本绘制
  • 全部文本绘制(即展开文本)绘制相对来说较为简单,只需将clickAttrText追加到attrText后,然后绘制

  • CTFrameRef、CTLines等的获取和上面一样,下面主要说下totalHeight计算及clickArea的计算

for (int i=0; i<lines.count; i++) {
        CTLineRef line = (__bridge CTLineRef)lines[i];
        totalHeight += [self heightForCTLine:line];
        // 绘制最后一行时
        if (i == lines.count - 1) {
            CTLineRef moreLine = CTLineCreateWithAttributedString((__bridge CFAttributedStringRef)self.clickAttributedText);
            
// 计算`收起`文本的origin.x值
            NSArray *runs = (NSArray*)CTLineGetGlyphRuns(line);
            CGFloat w = 0;
            for (int i=0; i<runs.count; i++) {
                if (i == runs.count - 1) {
                    break;
                }
                CTRunRef run = (__bridge CTRunRef)runs[i];
                w += CTRunGetTypographicBounds(run, CFRangeMake(0, 0), NULL, NULL, NULL);
            }
            
            CGSize moreSize = CTLineGetBoundsWithOptions(moreLine, 0).size;
            CGFloat h = moreSize.height + lines.count * _lineHeightErrorDimension;
            self.clickArea = CGRectMake(w, totalHeight - h, moreSize.width, h);
        }
    }

用法

@property (weak, nonatomic) IBOutlet HYExpandableLabel *expandableLabel;
// 高度值约束
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *expandableLabelHeightCons;

- (void)viewDidLoad {
    [super viewDidLoad];
    
    _expandableLabel.attributedText = [[NSAttributedString alloc] initWithString:@"测试文本😁"  attributes: @{ NSFontAttributeName:[UIFont systemFontOfSize:14] }];
        
    __block typeof(self)weakSelf = self;
    _expandableLabel.action = ^(HYExpandableLabelActionType type, id info) {
        if (type == HYExpandableLabelActionDidLoad) {
            NSLog(@"_expandableLabel Did Calculated");
// 更新布局
            weakSelf.expandableLabelHeightCons.constant = [info floatValue];
            [weakSelf.view layoutIfNeeded];
        }
    };
}

Github

https://github.com/BackWorld/HYExpandableLabel

如果对你有帮助,别忘了加个关注 或 点个哦😁

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容