主要参照 YYKit
YYKit 博大精深,就像少林武功
Async View
为了异步 + runloop 空闲时绘制,
因为苹果的 UILabel 性能不够 6
Async Layer
思路: UI 操作,必须放在主线程,
然而图形处理,可以放在子线程,
( 开辟图形上下文,进行绘制,取出图片 )
最后一步,放在主线程,就好了
layer.contents = image
Custom View 中, layer 类,重新制定为异步 layer
- + (Class)layerClass {
- return YYAsyncLayer.class;
- }
建立绘制任务
创建一个绘制任务,YYAsyncLayerDisplayTask
关键是里面的绘制方法 display
拿到异步图层 layer 创建的图形上下文,
调一下坐标系,( Core Text 的原点,在左下方 )
文本 map 为富文本,
富文本 map 为一帧,
一帧拆分为好多 CTLine,
一行一行地展示
- - (YYAsyncLayerDisplayTask *)newAsyncDisplayTask {
- // capture current state to display task
- NSString *text = _text;
- UIFont *fontX = _font;
- YYAsyncLayerDisplayTask *task = [YYAsyncLayerDisplayTask new];
- CGFloat h_h = self.bounds.size.height;
- CGFloat w_w = self.bounds.size.width;
- task.display = ^(CGContextRef context, CGSize size, BOOL(^isCancelled)(void)) {
- if (isCancelled()) return;
- //在这里由于绘制文字会颠倒
- [[NSOperationQueue mainQueue] addOperationWithBlock:^{
- CGContextSetTextMatrix(context, CGAffineTransformIdentity);
- CGContextTranslateCTM(context, 0, h_h);
- CGContextScaleCTM(context, 1.0, -1.0);
- }];
- NSAttributedString* str = [[NSAttributedString alloc] initWithString:text attributes:@{NSFontAttributeName: fontX, NSForegroundColorAttributeName: UIColor.blueColor}];
- CTFramesetterRef ref = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)str);
- CGPathRef path = CGPathCreateWithRect(CGRectMake(0, 0, w_w, 3000), nil);
- CTFrameRef pic = CTFramesetterCreateFrame(ref, CFRangeMake(0, 0), path, nil);
- CFArrayRef arr = CTFrameGetLines(pic);
- NSArray *array = (__bridge NSArray*)arr;
- int i = 0;
- int cnt = (int)array.count;
- CGPoint originsArray[cnt];
- CTFrameGetLineOrigins(pic, CFRangeMake(0, 0), originsArray);
- CGFloat y_y = h_h - 60;
- while (i < cnt) {
- NSLog(@"%f", originsArray[i].y);
- CTLineRef line = (__bridge CTLineRef)(array[i]);
- CGContextSetTextPosition(context, 0, y_y - i * 30);
- CTLineDraw(line, context);
- i += 1;
- }
- };
- return task;
- }
Async Layer 中, 启动绘制任务,
先处理下继承关系,
再执行上文提到的绘制任务
- - (void)display {
- super.contents = super.contents;
- [self _displayAsync];
- }
执行绘制任务,
拿到任务,没有绘制内容,就算了
再判断,自身的大小 ( size ), 合不合规
大小 CGSize(1, 1)
, 就继续,
子线程,先开辟图形上下文,
再处理背景色,
如果顺利,执行上文的绘制步骤,
从图形上下文中,取出 image, 交给 layer.contents
- - (void)_displayAsync{
- __strong id<YYAsyncLayerDelegate> delegate = (id)self.delegate;
- YYAsyncLayerDisplayTask *task = [delegate newAsyncDisplayTask];
- if (!task.display) {
- self.contents = nil;
- return;
- }
- 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);
- });
- }
- CGColorRelease(backgroundColor);
- return;
- }
- dispatch_async(YYAsyncLayerGetDisplayQueue(), ^{
- 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();
- return;
- }
- UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- if (isCancelled()) {
- return;
- }
- dispatch_async(dispatch_get_main_queue(), ^{
- if (isCancelled() == NO) {
- self.contents = (__bridge id)(image.CGImage);
- }
- });
- });
- }
RunLoop
触发
设置样式后,不会立即触发,重绘
先保存起来
- - (void)setText:(NSString *)text {
- _text = text.copy;
- [[YYTransaction transactionWithTarget:self selector:@selector(contentsNeedUpdated)] commit];
- }
调用异步图层的绘制任务
- - (void)contentsNeedUpdated {
- // do update
- [self.layer setNeedsDisplay];
- }
事件的保存
先把方法调用的 target 和 action, 保存为对象
YYTransactionSetup();
单例方法,初始化
把方法调用的对象,添加到集合
- @implementation YYTransaction
- + (YYTransaction *)transactionWithTarget:(id)target selector:(SEL)selector{
- if (!target || !selector) return nil;
- YYTransaction *t = [YYTransaction new];
- t.target = target;
- t.selector = selector;
- return t;
- }
- - (void)commit {
- if (!_target || !_selector) return;
- YYTransactionSetup();
- [transactionSet addObject:self];
- }
空闲的时候,把事情给办了,不影响帧率
下面的单例方法,初始化事件任务集合,
run loop 回调中,执行
不干涉, 主 runloop
- static NSMutableSet *transactionSet = nil;
- static void YYRunLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
- if (transactionSet.count == 0) return;
- NSSet *currentSet = transactionSet;
- transactionSet = [NSMutableSet new];
- [currentSet enumerateObjectsUsingBlock:^(YYTransaction *transaction, BOOL *stop) {
- [transaction.target performSelector:transaction.selector];
- }];
- }
- static void YYTransactionSetup() {
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- transactionSet = [NSMutableSet new];
- CFRunLoopRef runloop = CFRunLoopGetMain();
- CFRunLoopObserverRef observer;
- observer = CFRunLoopObserverCreate(CFAllocatorGetDefault(),
- kCFRunLoopBeforeWaiting | kCFRunLoopExit,
- true,
- 0xFFFFFF,
- YYRunLoopObserverCallBack, NULL);
- CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes);
- CFRelease(observer);
- });
- }
YYLabel
功能相当强大的渲染工具,
在上文异步渲染的基础上
支持各种样式
增加了一层抽象 YYTextLayout
YYLabel
中的绘制任务,
如果需要更新,就创建新的布局 layout ,
如果居中 / 底部对其,处理下 layout 布局
然后 layout 绘制
- @implementation YYLabel
- - (YYTextAsyncLayerDisplayTask *)newAsyncDisplayTask {
- // create display task
- YYTextAsyncLayerDisplayTask *task = [YYTextAsyncLayerDisplayTask new];
- task.display = ^(CGContextRef context, CGSize size, BOOL (^isCancelled)(void)) {
- if (isCancelled()) return;
- if (text.length == 0) return;
- YYTextLayout *drawLayout = layout;
- if (layoutNeedUpdate) {
- layout = [YYTextLayout layoutWithContainer:container text:text];
- shrinkLayout = [YYLabel _shrinkLayoutWithLayout:layout];
- if (isCancelled()) return;
- layoutUpdated = YES;
- drawLayout = shrinkLayout ? shrinkLayout : layout;
- }
- CGSize boundingSize = drawLayout.textBoundingSize;
- CGPoint point = CGPointZero;
- if (verticalAlignment == YYTextVerticalAlignmentCenter) {
- if (drawLayout.container.isVerticalForm) {
- point.x = -(size.width - boundingSize.width) * 0.5;
- } else {
- point.y = (size.height - boundingSize.height) * 0.5;
- }
- } else if (verticalAlignment == YYTextVerticalAlignmentBottom) {
- if (drawLayout.container.isVerticalForm) {
- point.x = -(size.width - boundingSize.width);
- } else {
- point.y = (size.height - boundingSize.height);
- }
- }
- point = YYTextCGPointPixelRound(point);
- [drawLayout drawInContext:context size:size point:point view:nil layer:nil debug:debug cancel:isCancelled];
- };
- return task;
- }
- @end
绘制各种
先绘制背景,
其次画阴影,
下划线,
文字,
图片
边框
- @implementation YYTextLayout
- - (void)drawInContext:(CGContextRef)context
- size:(CGSize)size
- point:(CGPoint)point
- view:(UIView *)view
- layer:(CALayer *)layer
- debug:(YYTextDebugOption *)debug
- cancel:(BOOL (^)(void))cancel{
- @autoreleasepool {
- if (self.needDrawBlockBorder && context) {
- if (cancel && cancel()) return;
- YYTextDrawBlockBorder(self, context, size, point, cancel);
- }
- if (self.needDrawBackgroundBorder && context) {
- if (cancel && cancel()) return;
- YYTextDrawBorder(self, context, size, point, YYTextBorderTypeBackgound, cancel);
- }
- if (self.needDrawShadow && context) {
- if (cancel && cancel()) return;
- YYTextDrawShadow(self, context, size, point, cancel);
- }
- if (self.needDrawUnderline && context) {
- if (cancel && cancel()) return;
- YYTextDrawDecoration(self, context, size, point, YYTextDecorationTypeUnderline, cancel);
- }
- if (self.needDrawText && context) {
- if (cancel && cancel()) return;
- YYTextDrawText(self, context, size, point, cancel);
- }
- if (self.needDrawAttachment && (context || view || layer)) {
- if (cancel && cancel()) return;
- YYTextDrawAttachment(self, context, size, point, view, layer, cancel);
- }
- if (self.needDrawInnerShadow && context) {
- if (cancel && cancel()) return;
- YYTextDrawInnerShadow(self, context, size, point, cancel);
- }
- if (self.needDrawStrikethrough && context) {
- if (cancel && cancel()) return;
- YYTextDrawDecoration(self, context, size, point, YYTextDecorationTypeStrikethrough, cancel);
- }
- if (self.needDrawBorder && context) {
- if (cancel && cancel()) return;
- YYTextDrawBorder(self, context, size, point, YYTextBorderTypeNormal, cancel);
- }
- if (debug.needDrawDebug && context) {
- if (cancel && cancel()) return;
- YYTextDrawDebug(self, context, size, point, debug);
- }
- }
- }
进入绘制文字
还有图片
这里的绘制粒度,比较上文,
粒度更加的细
上文是 CTLine,
这里是 CTRun
- // 注意条件判断,
- // 与保存 / 恢复图形上下文
- static void YYTextDrawAttachment(YYTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, UIView *targetView, CALayer *targetLayer, BOOL (^cancel)(void)) {
- BOOL isVertical = layout.container.verticalForm;
- CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0;
- for (NSUInteger i = 0, max = layout.attachments.count; i < max; i++) {
- YYTextAttachment *a = layout.attachments[i];
- if (!a.content) continue;
- UIImage *image = nil;
- UIView *view = nil;
- CALayer *layer = nil;
- if ([a.content isKindOfClass:[UIImage class]]) {
- image = a.content;
- } else if ([a.content isKindOfClass:[UIView class]]) {
- view = a.content;
- } else if ([a.content isKindOfClass:[CALayer class]]) {
- layer = a.content;
- }
- if (!image && !view && !layer) continue;
- if (image && !context) continue;
- if (view && !targetView) continue;
- if (layer && !targetLayer) continue;
- if (cancel && cancel()) break;
- CGSize asize = image ? image.size : view ? view.frame.size : layer.frame.size;
- CGRect rect = ((NSValue *)layout.attachmentRects[i]).CGRectValue;
- if (isVertical) {
- rect = UIEdgeInsetsInsetRect(rect, UIEdgeInsetRotateVertical(a.contentInsets));
- } else {
- rect = UIEdgeInsetsInsetRect(rect, a.contentInsets);
- }
- rect = YYTextCGRectFitWithContentMode(rect, asize, a.contentMode);
- rect = YYTextCGRectPixelRound(rect);
- rect = CGRectStandardize(rect);
- rect.origin.x += point.x + verticalOffset;
- rect.origin.y += point.y;
- if (image) {
- CGImageRef ref = image.CGImage;
- if (ref) {
- CGContextSaveGState(context);
- CGContextTranslateCTM(context, 0, CGRectGetMaxY(rect) + CGRectGetMinY(rect));
- CGContextScaleCTM(context, 1, -1);
- CGContextDrawImage(context, rect, ref);
- CGContextRestoreGState(context);
- }
- } else if (view) {
- view.frame = rect;
- [targetView addSubview:view];
- } else if (layer) {
- layer.frame = rect;
- [targetLayer addSublayer:layer];
- }
- }
- }
本文,最后一个问题:
上面 layout 的绘制信息,怎么取得的?
先拿文本,创建 CTFrame, CTFrame 中拿到 CTLine 数组
然后遍历每一行,与计算
- @implementation YYTextLayout
- + (YYTextLayout *)layoutWithContainer:(YYTextContainer *)container text:(NSAttributedString *)text range:(NSRange)range {
- // ...
- ctSetter = CTFramesetterCreateWithAttributedString((CFTypeRef)text);
- if (!ctSetter) goto fail;
- ctFrame = CTFramesetterCreateFrame(ctSetter, YYTextCFRangeFromNSRange(range), cgPath, (CFTypeRef)frameAttrs);
- if (!ctFrame) goto fail;
- lines = [NSMutableArray new];
- ctLines = CTFrameGetLines(ctFrame);
- // ...
- for (NSUInteger i = 0, max = lines.count; i < max; i++) {
- YYTextLine *line = lines[i];
- if (truncatedLine && line.index == truncatedLine.index) line = truncatedLine;
- if (line.attachments.count > 0) {
- [attachments addObjectsFromArray:line.attachments];
- [attachmentRanges addObjectsFromArray:line.attachmentRanges];
- [attachmentRects addObjectsFromArray:line.attachmentRects];
- for (YYTextAttachment *attachment in line.attachments) {
- if (attachment.content) {
- [attachmentContentsSet addObject:attachment.content];
- }
- }
- }
- }
- // ...
- }
github repo
到此这篇关于iOS两丫技术之UILabel性能不够的解决方法的文章就介绍到这了,更多相关iOS UILabe内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!