YYText框架 图片并排的源码实现

时间:2021-6-12 作者:qvyue

有时我们有的UI效果图如果是文字加图片混合在一起的, 如果使用UIImageView来拼接UILable的话后期扩展维护起来困难,这个时候我们可以使用富文本来实现:

YYText框架 图片并排的源码实现
图标文字混合.png
一、使用YYText框架实现

这里推荐使用YYText框架里面封装的api来实现,用别人已经封装得比较完善的会比较简单,见代码:

//   pod 'YYText', '~> 1.0.7'
- (void)viewDidLoad {
    [super viewDidLoad];
    NSString *leftDiamond = [NSString stringWithFormat:@"蓝钻余额:%@ ", @(600)];
    UIImage *image = [UIImage imageNamed:@"privacyChat_diamond"];
    
    NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
    style.alignment = NSTextAlignmentCenter;
    NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:leftDiamond attributes:@{NSForegroundColorAttributeName : [UIColor orangeColor], NSFontAttributeName : self.diamondLabel.font, NSParagraphStyleAttributeName : style}];
    NSAttributedString *attrStr_image = [NSAttributedString yy_attachmentStringWithContent:image contentMode:UIViewContentModeScaleAspectFit attachmentSize:CGSizeMake(16, 16) alignToFont:self.diamondLabel.font alignment:YYTextVerticalAlignmentCenter];
    [attrStr appendAttributedString:attrStr_image];
    self.diamondLabel.attributedText = attrStr;
}

- (YYLabel *)diamondLabel
{
    if (_diamondLabel == nil) {
        _diamondLabel = [[YYLabel alloc] initWithFrame:CGRectMake(10, 300, [UIScreen mainScreen].bounds.size.width - 20, 30)];
        _diamondLabel.userInteractionEnabled = YES;
        _diamondLabel.numberOfLines = 1;
        _diamondLabel.font = [UIFont systemFontOfSize:16];
        _diamondLabel.textVerticalAlignment = YYTextVerticalAlignmentCenter;
        _diamondLabel.backgroundColor = [UIColor clearColor];
    }
    return _diamondLabel;
}

由上面可以知道:
实现的方式是使用YYLable显示添加了图片attachmentNSMutableAttributedString.

二、YYText创建NSMutableAttributedString的方式
  1. 首先看拼接方法:
+ (NSMutableAttributedString *)yy_attachmentStringWithContent:(id)content
                                                  contentMode:(UIViewContentMode)contentMode
                                               attachmentSize:(CGSize)attachmentSize
                                                  alignToFont:(UIFont *)font
                                                    alignment:(YYTextVerticalAlignment)alignment{
// 1.初始化AttributedString为占位符YYTextAttachmentToken (= @"uFFFC");
    NSMutableAttributedString *atr = [[NSMutableAttributedString alloc] initWithString:YYTextAttachmentToken];
    YYTextAttachment *attach = [YYTextAttachment new];
    attach.content = content;
    attach.contentMode = contentMode;
// 2.将附件内容设置到atr中,内部调用[self yy_setAttribute:YYTextAttachmentAttributeName value:textAttachment range:range];
    [atr yy_setTextAttachment:attach range:NSMakeRange(0, atr.length)];
// 3.将附件大小及与文字对齐封装在YYTextRunDelegate中
    YYTextRunDelegate *delegate = [YYTextRunDelegate new];
    delegate.width = attachmentSize.width;
...
// 4.创建CTRunDelegate设置到atr中
    CTRunDelegateRef delegateRef = delegate.CTRunDelegate;
    [atr yy_setRunDelegate:delegateRef range:NSMakeRange(0, atr.length)]; 
    if (delegate) CFRelease(delegateRef);
    return atr;
}
YYText框架 图片并排的源码实现
文字对齐情况.png
三、 YYText如何绘制attachment和文字到YYLable中的?
  • YYLabel 的内部实现使用了YYTextAsyncLayer作为self.layer。
// @interface YYLabel : UIView
+ (Class)layerClass {
    return [YYTextAsyncLayer class];
}

- (void)setAttributedText:(NSAttributedString *)attributedText {
        省略... 
// 更新好属性之后,调用_setLayoutNeedUpdate去执行label的内容更新
        [self _setLayoutNeedUpdate];
}

- (void)_setLayoutNeedUpdate {
    _state.layoutNeedUpdate = YES;
    [self _clearInnerLayout];// 清除之前的布局
// 将layer设置为需要重绘(相当于dirty),系统会调用layer的-display方法进行内容重绘
    [self.layer setNeedsDisplay];
}

由上面可以知道,文字与附件attachment的绘制在YYTextAsyncLayer当中的
iOS UIView和CALayer

  • YYTextAsyncLayer绘制步骤
// 重写了- (void)display,这个方法在需要展示或者setNeedsDisplay时候会调用。
- (void)display {
    super.contents = super.contents;
    [self _displayAsync:_displaysAsynchronously];
}

- (void)_displayAsync:(BOOL)async {
// 1.创建DisplayTask任务,这里delegate是YYLable
    YYTextAsyncLayerDisplayTask *task = [delegate newAsyncDisplayTask];
    if (async) {// 如果是异步绘制
        ...
    }else{// 同步绘制
        if (task.willDisplay) task.willDisplay(self);        
        UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.opaque, self.contentsScale);
        CGContextRef context = UIGraphicsGetCurrentContext();
        task.display(context, self.bounds.size, ^{return NO;});
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        self.contents = (__bridge id)(image.CGImage);
        if (task.didDisplay) task.didDisplay(self, YES);
    }
}

从上面代码可以知道,绘制的步骤是:

  1. 调用willDisplay(self)。
  2. 创建图形上下文ImageContext,调用display这个block,将具体的内容绘制到ImageContext。
  3. 将ImageContext的内容设置为layer. contents
  4. 调用didDisplay(self, YES)。
  • 具体的绘制任务
task.display = ^(CGContextRef context, CGSize size, BOOL (^isCancelled)(void)) {
    YYTextLayout *drawLayout = layout;
    if (layoutNeedUpdate) {
// 1. 计算得出layout
        layout = [YYTextLayout layoutWithContainer:container text:text];
// 2. 根据文字行数去缩减layout
        shrinkLayout = [YYLabel _shrinkLayoutWithLayout:layout];
        if (isCancelled()) return;
        layoutUpdated = YES;
        drawLayout = shrinkLayout ? shrinkLayout : layout;
    }
    
    CGSize boundingSize = drawLayout.textBoundingSize;
    CGPoint point = CGPointZero;
    if (verticalAlignment == YYTextVerticalAlignmentCenter) {
        ...
    } else if (verticalAlignment == YYTextVerticalAlignmentBottom) {
        ...
    }
    point = YYTextCGPointPixelRound(point);
//3. 将drawLayout绘制到context中
    [drawLayout drawInContext:context size:size point:point view:nil layer:nil debug:debug cancel:isCancelled];
};
  • 绘制到context中具体做的什么
    因为YYLable中绘制的东西比较多(边框、背景色、阴影、下划线等),这里挑出文字绘制和附件绘制函数来说明。
// 1. 文字
static void YYTextDrawText(YYTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, BOOL (^cancel)(void)) {
    CGContextSaveGState(context); {
        
        CGContextTranslateCTM(context, point.x, point.y);
        CGContextTranslateCTM(context, 0, size.height);
        CGContextScaleCTM(context, 1, -1);
// ...
        NSArray *lines = layout.lines;
        for (NSUInteger l = 0, lMax = lines.count; l 
  • 文字YYTextLayout的计算
    YYText 源码剖析:CoreText 与异步绘制
声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:qvyue@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。