iOS两丫技术之UILabel性能不够的解决方法
主要参照 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]; } } } } // ... }
到此这篇关于iOS两丫技术之UILabel性能不够的解决方法的文章就介绍到这了,更多相关iOS UILabe内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
最新评论