一、涉及到的几个类
- YYLabel:Label控件,继承UIView
- YYTextLayout、YYTextContainer、YYTextLine:用于布局计算,它是YYLabel的属性,当我们为YYLabel设置属性时(比如text、textColor等),它会计算出布局信息和完成内容的绘制。
- YYTextAsyncLayer:自定义的Layer组件
二、流程
1 将YYLabel的默认Layer替换为YYTextAsyncLayer。
从上一篇文章中得知,我们只需重写+layerClass方法即可。
1 + (Class)layerClass { 2 return [YYTextAsyncLayer class]; 3 }
2 重写YYTextAsyncLayer的display方法
1 - (void)display { 2 super.contents = super.contents; 3 // _displaysAsynchronously属性表示是否需要异步绘制,默认是NO 4 [self _displayAsync:_displaysAsynchronously]; 5 }
从上一篇文章中得知,display方法用来设置contents属性,而我们可以通过为contents赋一个CGImage的值,将YYLabel的结果,显示出来。这就需要我们通过YYLabel的所有属性(比如text、textColor等)来绘制一个CGImage出来,绘制的动作就是通过下面的方法完成的,绘制可以同步进行(默认),也可以异步进行,提高性能。
1 - (void)_displayAsync:(BOOL)async { 2 // 暂时省略内容,后续后逐行分析 3 }
要想生存内容图片,我们需要知道布局信息。
布局信息的计算是通过YYLabel的YYTextLayout属性完成的。我们知道Layer的delegate是它的View(即YYTextAsyncLayer的delegate是YYLabel),我们为YYLabel实现一个newAsyncDisplayTask方法,YYTextAsyncLayer通过delegate调用该方法会返回一个YYTextAsyncLayerDisplayTask对象。通过YYTextAsyncLayerDisplayTask对象的block属性,YYTextAsyncLayer将上写文(context)回调给YYLabel的YYTextLayout属性,这时YYTextLayout的布局信息已经计算好,又获得了上下文信息(context),就可以完成绘制了。
3 YYTextLayout的布局计算方法和绘制方法
(1)布局计算方法
1 + (YYTextLayout *)layoutWithContainer:(YYTextContainer *)container text:(NSAttributedString *)text range:(NSRange)range;
(2)绘制方法
1 - (void)drawInContext:(nullable CGContextRef)context 2 size:(CGSize)size 3 point:(CGPoint)point 4 view:(nullable UIView *)view 5 layer:(nullable CALayer *)layer 6 debug:(nullable YYTextDebugOption *)debug 7 cancel:(nullable BOOL (^)(void))cancel;
4 YYTextLayout绘制好图片,YYTextAsyncLayer通过上下文(context)就能拿到图片,并设置给contents属性,这样整个Label的显示就完成了。
最后看一下YYTextAsyncLayer的生成图片的方法
1 - (void)_displayAsync:(BOOL)async { 2 __strong id<YYTextAsyncLayerDelegate> delegate = (id)self.delegate; 3 YYTextAsyncLayerDisplayTask *task = [delegate newAsyncDisplayTask]; 4 if (!task.display) { 5 if (task.willDisplay) task.willDisplay(self); 6 self.contents = nil; 7 if (task.didDisplay) task.didDisplay(self, YES); 8 return; 9 } 10 11 if (async) { 12 if (task.willDisplay) task.willDisplay(self); 13 _YYTextSentinel *sentinel = _sentinel; 14 int32_t value = sentinel.value; 15 BOOL (^isCancelled)() = ^BOOL() { 16 return value != sentinel.value; 17 }; 18 CGSize size = self.bounds.size; 19 BOOL opaque = self.opaque; 20 CGFloat scale = self.contentsScale; 21 CGColorRef backgroundColor = (opaque && self.backgroundColor) ? CGColorRetain(self.backgroundColor) : NULL; 22 if (size.width < 1 || size.height < 1) { 23 CGImageRef image = (__bridge_retained CGImageRef)(self.contents); 24 self.contents = nil; 25 if (image) { 26 dispatch_async(YYTextAsyncLayerGetReleaseQueue(), ^{ 27 CFRelease(image); 28 }); 29 } 30 if (task.didDisplay) task.didDisplay(self, YES); 31 CGColorRelease(backgroundColor); 32 return; 33 } 34 35 dispatch_async(YYTextAsyncLayerGetDisplayQueue(), ^{ 36 if (isCancelled()) { 37 CGColorRelease(backgroundColor); 38 return; 39 } 40 UIGraphicsBeginImageContextWithOptions(size, opaque, scale); 41 CGContextRef context = UIGraphicsGetCurrentContext(); 42 if (opaque) { 43 CGContextSaveGState(context); { 44 if (!backgroundColor || CGColorGetAlpha(backgroundColor) < 1) { 45 CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor); 46 CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale)); 47 CGContextFillPath(context); 48 } 49 if (backgroundColor) { 50 CGContextSetFillColorWithColor(context, backgroundColor); 51 CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale)); 52 CGContextFillPath(context); 53 } 54 } CGContextRestoreGState(context); 55 CGColorRelease(backgroundColor); 56 } 57 task.display(context, size, isCancelled); 58 if (isCancelled()) { 59 UIGraphicsEndImageContext(); 60 dispatch_async(dispatch_get_main_queue(), ^{ 61 if (task.didDisplay) task.didDisplay(self, NO); 62 }); 63 return; 64 } 65 UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 66 UIGraphicsEndImageContext(); 67 if (isCancelled()) { 68 dispatch_async(dispatch_get_main_queue(), ^{ 69 if (task.didDisplay) task.didDisplay(self, NO); 70 }); 71 return; 72 } 73 dispatch_async(dispatch_get_main_queue(), ^{ 74 if (isCancelled()) { 75 if (task.didDisplay) task.didDisplay(self, NO); 76 } else { 77 self.contents = (__bridge id)(image.CGImage); 78 if (task.didDisplay) task.didDisplay(self, YES); 79 } 80 }); 81 }); 82 } else { 83 [_sentinel increase]; 84 if (task.willDisplay) task.willDisplay(self); 85 UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.opaque, self.contentsScale); 86 CGContextRef context = UIGraphicsGetCurrentContext(); 87 if (self.opaque) { 88 CGSize size = self.bounds.size; 89 size.width *= self.contentsScale; 90 size.height *= self.contentsScale; 91 CGContextSaveGState(context); { 92 if (!self.backgroundColor || CGColorGetAlpha(self.backgroundColor) < 1) { 93 CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor); 94 CGContextAddRect(context, CGRectMake(0, 0, size.width, size.height)); 95 CGContextFillPath(context); 96 } 97 if (self.backgroundColor) { 98 CGContextSetFillColorWithColor(context, self.backgroundColor); 99 CGContextAddRect(context, CGRectMake(0, 0, size.width, size.height)); 100 CGContextFillPath(context); 101 } 102 } CGContextRestoreGState(context); 103 } 104 task.display(context, self.bounds.size, ^{return NO;}); 105 UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 106 UIGraphicsEndImageContext(); 107 self.contents = (__bridge id)(image.CGImage); 108 if (task.didDisplay) task.didDisplay(self, YES); 109 } 110 }