YYText源码解读-YYText同步/异步渲染流程(二)
程序员文章站
2023-12-23 12:55:10
...
一、涉及到的几个类
- YYLabel:Label控件,继承UIView
- YYTextLayout、YYTextContainer、YYTextLine:用于布局计算,它是YYLabel的属性,当我们为YYLabel设置属性时(比如text、textColor等),它会计算出布局信息和完成内容的绘制。
- YYTextAsyncLayer:自定义的Layer组件
二、流程
1 将YYLabel的默认Layer替换为YYTextAsyncLayer。
从上一篇文章中得知,我们只需重写+layerClass方法即可。
+ (Class)layerClass {
return [YYTextAsyncLayer class];
}
2 重写YYTextAsyncLayer的display方法
- (void)display {
super.contents = super.contents;
// _displaysAsynchronously属性表示是否需要异步绘制,默认是NO
[self _displayAsync:_displaysAsynchronously];
}
从上一篇文章中得知,display方法用来设置contents属性,而我们可以通过为contents赋一个CGImage的值,将YYLabel的结果,显示出来。这就需要我们通过YYLabel的所有属性(比如text、textColor等)来绘制一个CGImage出来,绘制的动作就是通过下面的方法完成的,绘制可以同步进行(默认),也可以异步进行,提高性能。
- (void)_displayAsync:(BOOL)async {
// 暂时省略内容,后续后逐行分析
}
要想生存内容图片,我们需要知道布局信息。
布局信息的计算是通过YYLabel的YYTextLayout属性完成的。我们知道Layer的delegate是它的View(即YYTextAsyncLayer的delegate是YYLabel),我们为YYLabel实现一个newAsyncDisplayTask方法,YYTextAsyncLayer通过delegate调用该方法会返回一个YYTextAsyncLayerDisplayTask对象。通过YYTextAsyncLayerDisplayTask对象的block属性,YYTextAsyncLayer将上写文(context)回调给YYLabel的YYTextLayout属性,这时YYTextLayout的布局信息已经计算好,又获得了上下文信息(context),就可以完成绘制了。
3 YYTextLayout的布局计算方法和绘制方法
###(1)布局计算方法
+ (YYTextLayout *)layoutWithContainer:(YYTextContainer *)container text:(NSAttributedString *)text range:(NSRange)range;
###(2)绘制方法
- (void)drawInContext:(nullable CGContextRef)context
size:(CGSize)size
point:(CGPoint)point
view:(nullable UIView *)view
layer:(nullable CALayer *)layer
debug:(nullable YYTextDebugOption *)debug
cancel:(nullable BOOL (^)(void))cancel;
4 YYTextLayout绘制好图片,YYTextAsyncLayer通过上下文(context)就能拿到图片,并设置给contents属性,这样整个Label的显示就完成了。
最后看一下YYTextAsyncLayer的生成图片的方法
- (void)_displayAsync:(BOOL)async {
__strong id<YYTextAsyncLayerDelegate> delegate = (id)self.delegate;
YYTextAsyncLayerDisplayTask *task = [delegate newAsyncDisplayTask];
if (!task.display) {
if (task.willDisplay) task.willDisplay(self);
self.contents = nil;
if (task.didDisplay) task.didDisplay(self, YES);
return;
}
if (async) {
if (task.willDisplay) task.willDisplay(self);
_YYTextSentinel *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(YYTextAsyncLayerGetReleaseQueue(), ^{
CFRelease(image);
});
}
if (task.didDisplay) task.didDisplay(self, YES);
CGColorRelease(backgroundColor);
return;
}
dispatch_async(YYTextAsyncLayerGetDisplayQueue(), ^{
if (isCancelled()) {
CGColorRelease(backgroundColor);
return;
}
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
CGContextRef context = UIGraphicsGetCurrentContext();
if (opaque) {
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 *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 {
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) {
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);
}
}