iOS学习专题程序员

从零到一撸个YYLabel

2017-11-09  本文已影响1079人  01_Jack

前言

在学习YYText过程中,分析完YYLabel原理后一时手痒,自己撸了个JKRichLabel,写文记录,也算功德圆满。相较于YYLabel,JKRichLabel更适合初学者通过阅读源码学习技术,毕竟大神的东西不好懂,有精力的童鞋强烈建议阅读YYLabel源码(虽然JKRichLabel更好懂,但是功力离YY大神差太远)

为保证界面流畅,各种技术层出不穷。JKRichLabel继承自UIView,基本复原了UILabel的功能特性,在此基础上采用压缩图层,异步绘制,可以更好的解决卡顿问题,并且内部通过core text绘制,支持图文混排。

JKRichLabel还很脆弱,欢迎感兴趣的童鞋一起完善ta

正文

效果图
Example.gif
设计思路
设计思路.png

以JKRichLabel为载体,JKAsyncLayer为核心,在JKRichLabelLayout中通过core text进行绘制。JKRichLabelLine是CTLine的拓展,包含一行要绘制的信息。JKTextInfo包含属性文本的基本信息,类似于CTRun。JKTextInfoContainer是JKTextInfo的容器,并且JKTextInfoContainer可以合并JKTextInfoContainer。同时,JKTextInfoContainer负责判断是否可以响应用户交互

@interface JKTextInfo : NSObject

@property (nonatomic, strong) NSAttributedString *text;
@property (nonatomic, strong) NSValue *rectValue;
@property (nonatomic, strong) NSValue *rangeValue;

@property (nullable, nonatomic, strong) JKTextAttachment *attachment;
@property (nullable, nonatomic, copy) JKTextBlock singleTap;
@property (nullable, nonatomic, copy) JKTextBlock longPress;

@property (nullable, nonatomic, strong) JKTextHighlight *highlight;
@property (nullable, nonatomic, strong) JKTextBorder *border;

@end
@interface JKTextInfoContainer : NSObject

@property (nonatomic, strong, readonly) NSArray<NSAttributedString *> *texts;
@property (nonatomic, strong, readonly) NSArray<NSValue *> *rects;
@property (nonatomic, strong, readonly) NSArray<NSValue *> *ranges;

@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, JKTextAttachment *> *attachmentDict;
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, JKTextBlock> *singleTapDict;
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, JKTextBlock> *longPressDict;

@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, JKTextHighlight *> *highlightDict;
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, JKTextBorder *> *borderDict;

@property (nullable, nonatomic, strong, readonly) JKTextInfo *responseInfo;

+ (instancetype)infoContainer;

- (void)addObjectFromInfo:(JKTextInfo *)info;
- (void)addObjectFromInfoContainer:(JKTextInfoContainer *)infoContainer;

- (BOOL)canResponseUserActionAtPoint:(CGPoint)point;

@end
JKAsyncLayer

JKAsyncLayer相较于YYTextAsyncLayer对部分逻辑进行调整,其余逻辑基本相同。JKAsyncLayer是整个流程中异步绘制的核心。

JKAsyncLayer继承自CALayer,UIView内部持有CALayer,JKRichLabel继承自UIView。因此,只要将JKRichLabel内部的layer替换成JKAsyncLayer就可以完成异步绘制。

+ (Class)layerClass {
    return [JKAsyncLayer class];
}

JKAsyncLayer绘制核心思想:在异步线程中获取context上下文,绘制背景色,生成image context,跳回主线程将image赋值给layer.contents。异步线程确保界面的流畅性,生成图片后赋值给contents可以压缩图层,同样能够提高界面的流畅性

self.contents = (__bridge id _Nullable)(img.CGImage);
JKRichLabel

JKRichLabel内部含有text与attributedText属性,分别支持普通文本与属性文本,不管是哪种文本,内部都转成属性文本_innerText,并通过_innerText进行绘制

- (void)setText:(NSString *)text {
    if (_text == text || [_text isEqualToString:text]) return;
    _text = text.copy;
    [_innerText replaceCharactersInRange:NSMakeRange(0, _innerText.length) withString:text];
    [self _update];
}

- (void)setAttributedText:(NSAttributedString *)attributedText {
    if (_attributedText == attributedText || [_attributedText isEqualToAttributedString:attributedText]) return;
    _attributedText = attributedText;
    _innerText = attributedText.mutableCopy;
    [self _update];
}
JKRichLabelLayout

JKRichLabelLayout是绘制具体内容的核心,通过core text可以完成attachment的绘制

CTFrameDraw(<#CTFrameRef  _Nonnull frame#>, <#CGContextRef  _Nonnull context#>)
CTLineDraw(<#CTLineRef  _Nonnull line#>, <#CGContextRef  _Nonnull context#>)
CTRunDraw(<#CTRunRef  _Nonnull run#>, <#CGContextRef  _Nonnull context#>, <#CFRange range#>)

可见,绘制图文混排必然要将attachment添加到CFAttributedStringRef中,然而并没有接口可以将attachment转换成字符串

Unicode字符列表.png
- (CTRunDelegateRef)runDelegate {
    CTRunDelegateCallbacks callbacks;
    callbacks.version = kCTRunDelegateCurrentVersion;
    callbacks.dealloc = JKTextRunDelegateDeallocCallback;
    callbacks.getAscent = JKTextRunDelegateGetAscentCallback;
    callbacks.getDescent = JKTextRunDelegateGetDescentCallback;
    callbacks.getWidth = JKTextRunDelegateGetWidthCallback;
    return CTRunDelegateCreate(&callbacks, (__bridge void *)self);
}
省略号说明

JKRichLabel的lineBreakMode暂不支持NSLineBreakByTruncatingHeadNSLineBreakByTruncatingMiddle,如果赋值为这两种属性,会自动转换为NSLineBreakByTruncatingTail

Long Text说明

效果图中有Long Text的例子,label外套scrollview,将scrollview的contentSize设置为label的size,label的size通过sizeToFit自动计算。如果文字足够长,这种方案就over了

Demo

上一篇下一篇

猜你喜欢

热点阅读