iOS兩丫技術(shù)之UILabel性能不夠的解決方法
主要參照 YYKit
YYKit 博大精深,就像少林武功
Async View
為了異步 + runloop 空閑時(shí)繪制,
因?yàn)樘O(píng)果的 UILabel 性能不夠 6
Async Layer
思路: UI 操作,必須放在主線(xiàn)程,
然而圖形處理,可以放在子線(xiàn)程,
( 開(kāi)辟圖形上下文,進(jìn)行繪制,取出圖片 )
最后一步,放在主線(xiàn)程,就好了
layer.contents = image
Custom View 中, layer 類(lèi),重新制定為異步 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ù),
子線(xiàn)程,先開(kāi)辟圖形上下文,
再處理背景色,
如果順利,執(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);
}
});
});
}RunLoop
觸發(fā)
設(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);
});
}YYLabel
功能相當(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繪制各種
先繪制背景,
其次畫(huà)陰影,
下劃線(xiàn),
文字,
圖片
邊框
@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];
}
}
}
}
// ...
}到此這篇關(guān)于iOS兩丫技術(shù)之UILabel性能不夠的解決方法的文章就介紹到這了,更多相關(guān)iOS UILabe內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- iOS基于 UILabel實(shí)現(xiàn)文字添加描邊功能
- iOS如何封裝帶復(fù)制功能的UILabel示例代碼
- iOS開(kāi)發(fā)總結(jié)之UILabel常用屬性介紹
- iOS中UILabel設(shè)置居上對(duì)齊、居中對(duì)齊、居下對(duì)齊及文字置頂顯示
- iOS動(dòng)態(tài)調(diào)整UILabel高度的幾種方法
- iOS UILabel 設(shè)置內(nèi)容的間距及高度的計(jì)算示例
- iOS中UILabel實(shí)現(xiàn)長(zhǎng)按復(fù)制功能實(shí)例代碼
- IOS 開(kāi)發(fā)之UILabel 或者 UIButton加下劃線(xiàn)鏈接
- iOS UILabel根據(jù)內(nèi)容自動(dòng)調(diào)整高度
相關(guān)文章
Objective-C 經(jīng)典字典數(shù)組排序 - 省市區(qū)
本文主要介紹Objective-C 字典數(shù)組排序,這里整理相關(guān)資料及實(shí)現(xiàn)示例代碼,有興趣的小伙伴可以參考下2016-09-09
UITableViewCell在編輯狀態(tài)下背景顏色的修改方法
這篇文章主要給大家介紹了關(guān)于UITableViewCell在編輯狀態(tài)下背景顏色的修改方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編一起來(lái)學(xué)習(xí)學(xué)習(xí)吧。2017-07-07
舉例講解iOS應(yīng)用開(kāi)發(fā)中hitTest觸摸事件的編寫(xiě)方法
這篇文章主要介紹了舉例講解iOS應(yīng)用開(kāi)發(fā)中hitTest觸摸事件的編寫(xiě)方法,重點(diǎn)講解了兩個(gè)view之間的事件傳遞,需要的朋友可以參考下2016-04-04
iOS使用UIBezierPath實(shí)現(xiàn)ProgressView
這篇文章主要為大家詳細(xì)介紹了iOS使用UIBezierPath實(shí)現(xiàn)ProgressView,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
IOS 開(kāi)發(fā)之PickerView文字和隨機(jī)數(shù)的使用
這篇文章主要介紹了IOS 開(kāi)發(fā)之PickerView文字和隨機(jī)數(shù)的使用的相關(guān)資料,這里提供實(shí)例幫助大家理解掌握這部分內(nèi)容,需要的朋友可以參考下2017-08-08
iOS實(shí)現(xiàn)多個(gè)垂直滑動(dòng)條并列視圖
這篇文章主要為大家詳細(xì)介紹了iOS實(shí)現(xiàn)多個(gè)垂直滑動(dòng)條并列視圖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03

