ios:Layer 异步绘制

1:layoutSubviews调用时机

  1. init不会触发layoutSubviews
  2. 调用addSubview会触发layoutSubviews
  3. UIView的Frame改变时(frame的值设置前后发生了变化),会触发layoutSubviews
  4. UIScrollView滚动时,UIView的重新布局会触发layoutSubviews
  5. 直接调用setNeedsLayout 或者layoutIfNeeded
  6. UILable,UIImageView等有内容物的View,调用sizeToFit方法后,size改变,也会触发

2:drawRect:调用时机

UIImageView的子类,重写drawRect方法,也不会调用drawRect方法的

每一个UIView都有一个layer,每一个layer都有个contents,这个content指向的是一块缓存,叫做backing store。
默认情况下,CALayer的content为空,若没有重写drawRect,content一直为空。
若UIView的子类重写了drawRect,则UIView执行完drawRect后,系统会为器layer的content开辟一块缓存,缓存大小为size = widthheightscale,用来存放drawRect绘制的内容。
即使重写的drawRect啥也没做,也会开辟缓存,消耗内存,所以尽量不要随便重写drawRect却啥也不做。也不要去调用drawRect,因为drawRect不是让你调用的,而是系统会去调用的.

- (void)viewDidLoad {
    [super viewDidLoad];
    self.obview = [[OBView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
    self.obview.backgroundColor = [UIColor cyanColor];
    [self.view addSubview:self.obview];
    
    NSLog(@"1: %@",self.obview.layer.contents);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"scale:%0.0f",[[UIScreen mainScreen] scale]);
        NSLog(@"2: %@",self.obview.layer.contents);
    });
}

@implementation OBView
// 
- (void)drawRect:(CGRect)rect {
	NSLog(@"%s",__func__);
}

//打印
2020-07-30 16:30:10.342673+0800 Test[26213:11584980] 1: (null)
2020-07-30 16:30:10.348338+0800 Test[26213:11584980] -[OBView drawRect:]
2020-07-30 16:30:11.342911+0800 Test[26213:11584980] scale:2
2020-07-30 16:30:11.343157+0800 Test[26213:11584980] 2: <CABackingStore 0x7fbecbf09970 (buffer [200 200] BGRX8888)>

那么怎样让系统调用drawRect去绘制呢?

  1. UIView初始化时没有设置rect,那么drawRect不被自动调用。因为drawRect 是在Controller->loadView, Controller->viewDidLoad 两方法之后调用的
  2. UILable,UIImageView等有内容物的View,调用sizeToFit方法后计算出最佳的size。然后系统自动调用drawRect方法重新绘制。
  3. 调用setNeedsDisplay和setNeedsDisplayInRect:都会触发drawRect(rect不能为0)

sizeToFit:会计算出最优的 size 而且会改变自己的size
sizeThatFits:会计算出最优的 size 但是不会改变 自己的 size

3:displayLayer方法

异步绘制的函数入口
displayLayer优先级高于drawRect,就是同时重写了两个方法,只会执行displayLayer。除了UIImageView,其他view都可以异步绘制

1:view绘制流程

  1. CALayer内部创建一个backing store(CGContextRef)();
    判断layer是否有代理;
  2. 有代理:调用delegete的drawLayer:inContext, 然后在合适的实际回调代理, 在[UIView drawRect]中做一些绘制工作;
  3. 没有代理:调用layer的drawInContext方法,
    layer上传backingStore到GPU, 结束系统的绘制流程;

ios:Layer 异步绘制_第1张图片

2:异步绘制

展示界面的过程中将创建上下午和控件的绘制工作放到子线程中, 子线程将那些工作完成渲染成图片后转回主线程然后将图片展示在界面上;

异步绘制的入口在[layer.delegate displayLayer]
异步绘制过程中代理负责生成对应的位图(bitmap);
将bitmap赋值给layer.content属性;


- (void)displayLayer:(CALayer *)layer {
    NSLog(@"%s",__func__);
    CGRect bounds = self.bounds;
    dispatch_async(dispatch_queue_create("com.ob", DISPATCH_QUEUE_CONCURRENT), ^{
        UIImage *image = [UIImage imageNamed:@"test.png"];
        UIGraphicsBeginImageContext(bounds.size);
        [image drawInRect:bounds];
        UIImage *newImg = UIGraphicsGetImageFromCurrentImageContext();
        dispatch_async(dispatch_get_main_queue(), ^{
            self.layer.contents = (__bridge id)(newImg.CGImage);
        });
    });
}


- (void)viewDidLoad {
    [super viewDidLoad];
//    self.obview = [[OBView allocWithZone:nil] init];
//    self.obview.frame = CGRectMake(100, 100, 100, 100);
    self.obview = [[OBView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
    self.obview.backgroundColor = [UIColor cyanColor];
    [self.view addSubview:self.obview];
    //必须调用,不然不会触发 displayLayer方法
    [self.obview.layer setNeedsDisplay];
}

还有UIimageview的高效圆角

- (void)cornerRadius:(CGFloat)radiu {
    UIGraphicsBeginImageContext(self.bounds.size);
    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:radiu];
    [path addClip];
    [self drawRect:self.bounds];
    self.image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
}

4:UIImageView和非UIImageView

UIImageView 非UIImageView
重写drawRect和displayLayer也不执行,除非显示调用 可以执行
异步绘制 不行 可以异步绘制

你可能感兴趣的:(iOS进阶)