您好,登錄后才能下訂單哦!
今天小編給大家分享一下iOS之UILabel性能不夠如何解決的相關(guān)知識(shí)點(diǎn),內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識(shí),所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來(lái)了解一下吧。
為了異步 + runloop 空閑時(shí)繪制,
因?yàn)樘O果的 UILabel 性能不夠 6
思路: UI 操作,必須放在主線程,
然而圖形處理,可以放在子線程,
( 開辟圖形上下文,進(jìn)行繪制,取出圖片 )
最后一步,放在主線程,就好了
layer.contents = image
Custom View 中, layer 類,重新制定為異步 layer
+ (Class)layerClass { return YYAsyncLayer.class; }
建立繪制任務(wù)
創(chuàng)建一個(gè)繪制任務(wù),YYAsyncLayerDisplayTask
關(guān)鍵是里面的繪制方法 display
拿到異步圖層 layer 創(chuàng)建的圖形上下文,
調(diào)一下坐標(biāo)系,( Core Text 的原點(diǎn),在左下方 )
文本 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; //在這里由于繪制文字會(huì)顛倒 [[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 中, 啟動(dòng)繪制任務(wù),
先處理下繼承關(guān)系,
再執(zhí)行上文提到的繪制任務(wù)
- (void)display { super.contents = super.contents; [self _displayAsync]; }
執(zhí)行繪制任務(wù),
拿到任務(wù),沒(méi)有繪制內(nèi)容,就算了
再判斷,自身的大小 ( size ), 合不合規(guī)
大小 CGSize(1, 1)
, 就繼續(xù),
子線程,先開辟圖形上下文,
再處理背景色,
如果順利,執(zhí)行上文的繪制步驟,
從圖形上下文中,取出 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); } }); }); }
設(shè)置樣式后,不會(huì)立即觸發(fā),重繪
先保存起來(lái)
- (void)setText:(NSString *)text { _text = text.copy; [[YYTransaction transactionWithTarget:self selector:@selector(contentsNeedUpdated)] commit]; }
調(diào)用異步圖層的繪制任務(wù)
- (void)contentsNeedUpdated { // do update [self.layer setNeedsDisplay]; }
先把方法調(diào)用的 target 和 action, 保存為對(duì)象
YYTransactionSetup();
單例方法,初始化
把方法調(diào)用的對(duì)象,添加到集合
@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]; }
空閑的時(shí)候,把事情給辦了,不影響幀率
下面的單例方法,初始化事件任務(wù)集合,
run loop 回調(diào)中,執(zhí)行
不干涉, 主 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); }); }
功能相當(dāng)強(qiáng)大的渲染工具,
在上文異步渲染的基礎(chǔ)上
支持各種樣式
增加了一層抽象 YYTextLayout
YYLabel
中的繪制任務(wù),
如果需要更新,就創(chuàng)建新的布局 layout ,
如果居中 / 底部對(duì)其,處理下 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); } } }
進(jìn)入繪制文字
還有圖片
這里的繪制粒度,比較上文,
粒度更加的細(xì)
上文是 CTLine,
這里是 CTRun
// 注意條件判斷, // 與保存 / 恢復(fù)圖形上下文 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]; } } }
本文,最后一個(gè)問(wèn)題:
上面 layout 的繪制信息,怎么取得的?
先拿文本,創(chuàng)建 CTFrame, CTFrame 中拿到 CTLine 數(shù)組
然后遍歷每一行,與計(jì)算
@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性能不夠如何解決”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會(huì)為大家更新不同的知識(shí),如果還想學(xué)習(xí)更多的知識(shí),請(qǐng)關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。