当前位置:网站首页>为什么要造一个 UILabel ?( 复习两丫技术 )
为什么要造一个 UILabel ?( 复习两丫技术 )
2022-07-23 11:54:00 【dengjiangszhan】
主要参照 YYKit
YYKit 博大精深,就像少林武功
a, Async View
为了异步 + runloop 空闲时绘制,
因为苹果的 UILabel 性能不够 6
1, 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);
}
});
});
}
2, 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);
});
}
b, 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
边栏推荐
- Comparison of functional characteristics and parameters of several solar panel battery charging management ICs cs5363, cs5350 and cs5328
- 剑指 Offer II 115. 重建序列 : 拓扑排序构造题
- 远程系统命令执行
- 【运维】ssh tunneling 依靠ssh的22端口实现访问远程服务器的接口服务
- Without Huawei, Qualcomm will raise prices at will, and domestic mobile phones that lack core technology can only be slaughtered
- Mathematical Modeling Typesetting
- Redis installation
- Mathematical Modeling Typesetting
- 关于初始化page入参的设计思路
- 黑马程序员-接口测试-四天学习接口测试-第三天-postman高级用法,newman例集导出导入,常用断言,断言json数据,工作原理,全局,环境变量,时间戳,请求前置脚本,关联,批量执行测试用例
猜你喜欢
![Php:filter pseudo protocol [bsidescf 2020]had a bad day](/img/ad/1e23fadb3f1ce36b297aaa767d9099.png)
Php:filter pseudo protocol [bsidescf 2020]had a bad day

【攻防世界WEB】难度三星9分入门题(上):simple_js、mfw

Gear 月度更新|6 月

CS5363,CS5350,CS5328几款太阳能板电池充电管理IC的功能特性与参数对比

Custom encapsulation pop-up box (with progress bar)

Summary of server performance tuning experience

js过滤/替换敏感字符

Bean Validation起源篇----01

【论文学习】《Source Mixing and Separation Robust Audio Steganography》

Backup content hahaha
随机推荐
MD5 strong collision, secondary decoding,
软件测试周刊(第81期):能够对抗消极的不是积极,而是专注;能够对抗焦虑的不是安慰,而是具体。
任务切换的细节
中年危机,35岁被退休,打工人拿什么来抗衡资本家?
Custom encapsulation pop-up box (with progress bar)
[cloud native] continuous integration and deployment (Jenkins)
Expression du suffixe (une question par jour pendant les vacances d'été 4)
Packaging and use of alamofire framework
Will the redis delete key command cause blocking?
Redis installation
现代商业无代码开发平台的治理和网络安全
It's too hard! Tencent T4 boss was still staying up late at 4 a.m. and was actually sorting out the distributed transaction notes
关于初始化page入参的设计思路
Quickly master QML Chapter 5 components
lc marathon 7.23
After Effects 教程,如何在 After Effects 中创建动画?
数字化转型时代的企业数据新基建 | 爱分析报告
Without Huawei, Qualcomm will raise prices at will, and domestic mobile phones that lack core technology can only be slaughtered
冒泡排序-看着一篇就够啦
Bean Validation入门篇----02