/**
The text displayed by the label. Default is nil.
Set a new value to this property also replaces the text in `attributedText`.
Get the value returns the plain text in `attributedText`.
*/
@property (nullable, nonatomic, copy) NSString *text;
YYLabel
给的注释是,给Text赋值之后,Get时返回的实际是一个attributedText
。
那么YYLabel
是怎么在UIView上把文字显示上去的呢?
在YYLabel
的text
Setter如下
/**
*开始添加注释解释 刚开始读代码的时候,很疑惑text被作者赋到哪里去了,实际上
[_innerText replaceCharactersInRange:NSMakeRange(0, _innerText.length) withString:text ? text : @""];
_innerText就是text的接受者,是一个NSMutableAttributerString类型
*/
- (void)setText:(NSString *)text {
if (_text == text || [_text isEqualToString:text]) return;
_text = text.copy;
BOOL needAddAttributes = _innerText.length == 0 && text.length > 0;
[_innerText replaceCharactersInRange:NSMakeRange(0, _innerText.length) withString:text ? text : @""];
// 这里作者是为了解决一个Bug 这是作者的解释 https://github.com/ibireme/YYText/issues/104
[_innerText removeDiscontinuousAttributesInRange:NSMakeRange(0, _innerText.length)];
// 是否需要添加属性 这个咱不管他就当 NO
if (needAddAttributes) {
_innerText.font = _font;
_innerText.color = _textColor;
_innerText.shadow = [self _shadowFromProperties];
_innerText.alignment = _textAlignment;
switch (_lineBreakMode) {
case NSLineBreakByWordWrapping:
case NSLineBreakByCharWrapping:
case NSLineBreakByClipping: {
_innerText.lineBreakMode = _lineBreakMode;
} break;
case NSLineBreakByTruncatingHead:
case NSLineBreakByTruncatingTail:
case NSLineBreakByTruncatingMiddle: {
_innerText.lineBreakMode = NSLineBreakByWordWrapping;
} break;
default: break;
}
}
// 这个里面是一系列的正则,判断串中是否有能够匹配到的、特定条件的内容,匹配到了,会执行特定的操作
// 因为在匹配前会把所有的Attribute移除,所以最后还需要重新对外面给的Attribute添加回去。
if ([_textParser parseText:_innerText selectedRange:NULL]) {
// 由此执行的update操作
[self _updateOuterTextProperties];
}
// 这里决定这个文字如何显示 才是重点
if (!_ignoreCommonProperties) {// 该值默认为NO,
// 如果是异步展示
if (_displaysAsynchronously && _clearContentsBeforeAsynchronouslyDisplay) {
// 清空当前Layer中Content CGImageRef image = (__bridge_retained CGImageRef)(self.layer.contents);
self.layer.contents = nil;
// 实际上当前layer是YYAsyncLayer 其内容最终是以绘制出来的image展示的
[self _clearContents];
}
[self _setLayoutNeedUpdate];// 此方法触发当前Layer的重绘 重点关注此方法
[self _endTouch];// 暂时忽略此方法
[self invalidateIntrinsicContentSize];// UIKit API 告知UIView其内容size已发生变化
}
}
最终的显示是由_setLayoutNeedUpdate
决定的,此方法如下:
// 这
- (void)_setLayoutNeedUpdate {
_state.layoutNeedUpdate = YES;
// 清空
[self _clearInnerLayout];
// 重绘
[self _setLayoutNeedRedraw];
}
_setLayoutNeedRedraw
又下
- (void)_setLayoutNeedRedraw {
// 触发layer重绘
[self.layer setNeedsDisplay];
}
下面开始重点看YYAsyncLayer
,在调用setNeedsDisplay
之后,layer
中都做了什么?
YYAsyncLayer
继承自CALayer
,在调用setNeedsDisplay
时,会触发layer
重绘。
作者的注释如下
The YYAsyncLayer class is a subclass of CALayer used for render contents asynchronously.
@discussion When the layer need update it's contents, it will ask the delegate
for a async display task to render the contents in a background queue.
当layer需要更新其内容时,会让其代理在异步线程中渲染其内容
// 是否异步渲染,默认为YES
@property BOOL displaysAsynchronously;
/**
The YYAsyncLayer's delegate protocol. The delegate of the YYAsyncLayer (typically a UIView)
must implements the method in this protocol.
*/
@protocol YYAsyncLayerDelegate <NSObject>
@required
/// This method is called to return a new display task when the layer's contents need update.
- (YYAsyncLayerDisplayTask *)newAsyncDisplayTask;
@end
在YYAsyncLayer
中的内容需要被更新时,会调用该代理方法,返回了一个YYAsyncLayerDisplayTask
对象,包含了绘制过程中的几种状态
// 在开始渲染内容之前,会在主线程调用该block 参数就是当前的layer
@property (nullable, nonatomic, copy) void (^willDisplay)(CALayer *layer);
// 在绘制的时候被调用
@property (nullable, nonatomic, copy) void (^display)(CGContextRef context, CGSize size, BOOL(^isCancelled)(void));
// 绘制完成后被调用
@property (nullable, nonatomic, copy) void (^didDisplay)(CALayer *layer, BOOL finished);
再来看YYAsyncLayer
中的内部实现,调用setNeedsDisplay
会触发CALayer
的重绘,那重绘又会调用哪个方法呢?
// 苹果文档解释是 Reloads the content of this layer.
// Do not call this method directly. 像UIView的draw方法一样,外部不能直接调用该方法
// 但是该方法可以在子类中被重写 YYAsyncLayer中重写了该方法,也就是YYLabel中的文本内容在这里被渲染出来
- (void)display;
// _displayAsync中实现了内容的渲染
- (void)display {
super.contents = super.contents;
[self _displayAsync:_displaysAsynchronously];
}
// 开始逐行解析 参数async 为是否异步渲染
- (void)_displayAsync:(BOOL)async {
// 获取delegate
__strong id<YYAsyncLayerDelegate> delegate = (id)self.delegate;
// 获取YYAsyncLayerDisplayTask对象
YYAsyncLayerDisplayTask *task = [delegate newAsyncDisplayTask];
// 判断外部是否给与了绘制的上下文等信息 如果没有会进入if语句 直接return。
if (!task.display) {
// 回到给代理 当前layer层
if (task.willDisplay) task.willDisplay(self);
// 清空contents
self.contents = nil;
// 完毕
if (task.didDisplay) task.didDisplay(self, YES);
return;
}
// 执行到此处 说明外部给了相应的上下文、size的信息 如果是异步绘制会进入下面的if语句
if (async) {
if (task.willDisplay) task.willDisplay(self);
YYSentinel *sentinel = _sentinel;
int32_t value = sentinel.value;
BOOL (^isCancelled)() = ^BOOL() {
return value != sentinel.value;
};
CGSize size = self.bounds.size;
BOOL opaque = self.opaque;
CGFloat scale = self.contentsScale;
CGColorRef backgroundColor = (opaque && self.backgroundColor) ? CGColorRetain(self.backgroundColor) : NULL;
if (size.width < 1 || size.height < 1) {
CGImageRef image = (__bridge_retained CGImageRef)(self.contents);
self.contents = nil;
if (image) {
dispatch_async(YYAsyncLayerGetReleaseQueue(), ^{
CFRelease(image);
});
}
if (task.didDisplay) task.didDisplay(self, YES);
CGColorRelease(backgroundColor);
return;
}
// 异步绘制从这里开始
dispatch_async(YYAsyncLayerGetDisplayQueue(), ^{
if (isCancelled()) {
CGColorRelease(backgroundColor);
return;
}
// 开启图形上下文
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
// 获取当前上下文
CGContextRef context = UIGraphicsGetCurrentContext();
if (opaque && context) {
// CGContextSaveGState函数的作用是将当前图形状态推入堆栈。
// 之后对图形状态所做的修改会影响随后的描画操作,但不影响存储在堆栈中的拷贝
// 在修改完成后可以通过CGContextRestoreGState函数把堆栈顶部的状态弹出,返回到之前的图形状态。
// 这种推入和弹出的方式是回到之前图形状态的快速方法,避免逐个撤消所有的状态修改
// 这也是将某些状态(比如裁剪路径)恢复到原有设置的唯一方式。
CGContextSaveGState(context); {
// 没有背景色或有透明度 会默认设置为白色背景色
if (!backgroundColor || CGColorGetAlpha(backgroundColor) < 1) {
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));
CGContextFillPath(context);
}
if (backgroundColor) {
CGContextSetFillColorWithColor(context, backgroundColor);
CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));
CGContextFillPath(context);
}
} CGContextRestoreGState(context);
CGColorRelease(backgroundColor);
}
task.display(context, size, isCancelled);
// 如果被取消 会直接关闭当前上下文 结束绘制
if (isCancelled()) {
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
if (task.didDisplay) task.didDisplay(self, NO);
});
return;
}
// 没有被取消 会获取当前绘制的UIImage
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if (isCancelled()) {
dispatch_async(dispatch_get_main_queue(), ^{
if (task.didDisplay) task.didDisplay(self, NO);
});
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
if (isCancelled()) {
if (task.didDisplay) task.didDisplay(self, NO);
} else {
// 最终在主线程中 将绘制好的文本图片给layer的contents进行显示 至此文本绘制完成
// contents为什么可以显示图片?这是苹果文档/** Layer content properties and methods. **/
/* An object providing the contents of the layer, typically a CGImageRef,
* but may be something else. (For example, NSImage objects are
* supported on Mac OS X 10.6 and later.) Default value is nil.
* Animatable. */
self.contents = (__bridge id)(image.CGImage);
if (task.didDisplay) task.didDisplay(self, YES);
}
});
});
} else {
// 非异步绘制 就是在主线程执行的上述操作
[_sentinel increase];
if (task.willDisplay) task.willDisplay(self);
UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.opaque, self.contentsScale);
CGContextRef context = UIGraphicsGetCurrentContext();
if (self.opaque && context) {
CGSize size = self.bounds.size;
size.width *= self.contentsScale;
size.height *= self.contentsScale;
CGContextSaveGState(context); {
if (!self.backgroundColor || CGColorGetAlpha(self.backgroundColor) < 1) {
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextAddRect(context, CGRectMake(0, 0, size.width, size.height));
CGContextFillPath(context);
}
if (self.backgroundColor) {
CGContextSetFillColorWithColor(context, self.backgroundColor);
CGContextAddRect(context, CGRectMake(0, 0, size.width, size.height));
CGContextFillPath(context);
}
} CGContextRestoreGState(context);
}
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);
}
}
上述内容是一行一行代码对YYAsyncLayer进行的分析,从YYLabel
的text
赋值,到最后显示。
其实到这里,还是会有疑惑。文本呢?为什么没有看到相应的文本的赋值?
这时候需要重新回到YYLabel
中看到。
// YYLabel的layer被替换为YYAsynvLayer
+ (Class)layerClass {
return [YYAsyncLayer class];
}
补充一点,在iOS中每个View在init
的时候,会同时创建一个CALayer,该Layer实际展示的内容就是最终UIView所能展示的内容,文本的渲染顺序是UIVIew -> init - > CALayer -> init ->display -> UIView -> drawRect,UIView作为CALayer的代理,如下
************
* UIView * // UIView更像是一个内容显示(Layer)和
// 事件(UIResponder)处理的管理者
************
↓ ↓
↓ ↓
UIResponder CALayer
那么文本的最终渲染就是在Layer层上,但是我还是没明白,文本是怎么给Layer并让Layer绘制的呢?在YYAsyncLayer
中,只看到了绘制,并没有看到传入的要绘制的内容。
网友评论