UI绘制原理
1、UIView的绘制流程
对这个流程进行下说明:
1、
当我们调用[UIView setNeedsDisplay]
方法时,并没有执行立即执行绘制工作;
2、
而是马上调用[view.layer setNeedsDisplay]
方法,给当前layer打上脏标记;
3、
在当前RunLoop快要结束的时候
调用[layer display]
方法,来进入到当前视图的真正绘制当中;
4、
在[layer display]
方法内部,系统会判断layer的layer.delegate是否能响应[layer.delegate respondsToSelector:@selector(displayLayer:)]
方法,
a. 如果没有实现,则执行系统的绘制流程;
b. 如果实现了则会进入异步绘制的入口;
5、
最后把绘制完的backing store(可以理解为位图)提交给GPU
2、系统绘制流程
系统绘制流程对这个流程进行下说明:
1、
在layer内部
会创建一个backing store
,我们可以理解为CGContextRef上下文;
2、
判断layer是否有delegate
a. 如果有delegate
,则会执行[layer.delegate drawLayer:inContext]
(这个方法的执行是在系统内部执行的),然后在这个方法中会调用[view drawRect:]
方法,也就是我们重写view的drawRect:方法才会被调用到。
b. 如果没有delegate,会调用layer的drawInContext方法,也就是我们可以重写的layer的该方法,此刻会被调用到
3、
最后把绘制完的backing store(可以理解为位图)提交给GPU。
3、异步绘制实现
异步绘制实现1、
某个时机调用setNeedsDisplay;
2、
runloop将要结束时调用[CALayer display];
3、
若代理实现了displayLayer将会调用此方法,在子线程中做异步绘制的工作;
4、
在子线程中创建上下文、绘制控件并生成图片;
5、
在主线程中设置layer.contents,将生成的视图展示在layer上。
示例:
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface AsyncDrawLabel : UIView
@property (nonatomic, copy) NSString *text;
@property (nonatomic, strong) UIFont *font;
@end
NS_ASSUME_NONNULL_END
#import "AsyncDrawLabel.h"
#import <CoreText/CoreText.h>
@implementation AsyncDrawLabel
- (void)setText:(NSString *)text {
_text = text;
}
- (void)setFont:(UIFont *)font {
_font = font;
}
// 除了在drawRect方法中, 其他地方获取context需要自己创建[https://www.jianshu.com/p/86f025f06d62] coreText用法简介:[https://www.cnblogs.com/purple-sweet-pottoes/p/5109413.html]
- (void)displayLayer:(CALayer *)layer {
CGSize size = self.bounds.size;
CGFloat scale = [UIScreen mainScreen].scale;
// 异步绘制,切换至子线程
dispatch_async(dispatch_get_global_queue(0, 0), ^{
UIGraphicsBeginImageContextWithOptions(size, NO, scale);
// 获取当前上下文
CGContextRef context = UIGraphicsGetCurrentContext();
[self draw:context size:size];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// 子线程完成工作,切换至主线程显示
dispatch_async(dispatch_get_main_queue(), ^{
self.layer.contents = (__bridge id)image.CGImage;
});
});
}
- (void)draw:(CGContextRef)context size:(CGSize)size {
// 将坐标系上下翻转,因为底层坐标系和 UIKit 坐标系原点位置不同。
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
// 文本沿着Y轴移动
CGContextTranslateCTM(context, 0, size.height); // 原点为左下角
// 文本反转成context坐标系
CGContextScaleCTM(context, 1, -1);
// 创建绘制区域
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0, 0, size.width, size.height));
// 创建需要绘制的文字
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc]initWithString:self.text];
[attrStr addAttribute:NSFontAttributeName value:self.font range:NSMakeRange(0, self.text.length)];
// 根据attStr生成CTFramesetterRef
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrStr);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attrStr.length), path, NULL);
// 将frame的内容绘制到content中
CTFrameDraw(frame, context);
}
@end
#import "ViewController.h"
#import "AsyncDrawLabel.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
AsyncDrawLabel *label = [[AsyncDrawLabel alloc] initWithFrame:CGRectMake(100, 100, 200, 100)];
label.backgroundColor = [UIColor yellowColor];
label.text = @"异步绘制text";
label.font = [UIFont systemFontOfSize:16];
[self.view addSubview:label];
[label.layer setNeedsDisplay]; // 不调用的话不会触发displayLayer方法
}
@end