ios:Layer 异步绘制

本文深入解析了iOS开发中布局与绘制的核心概念,包括layoutSubviews、setNeedsLayout、drawRect及displayLayer的调用时机与作用,揭示了UIView、UIImageView等控件在不同情况下的行为差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1:layoutSubviews调用时机

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

1: setNeedsLayout调用该方法,不管rect有没有变化,都会执行layoutSubviews,标记视图,在下次loop调用;

2: layoutIfNeeded :只有在rect变化时才会触发layoutSubviews,并且是立马执行(当前loop中)

- (void)testLayout {
    OBScrollView *scrollView = [[OBScrollView alloc] initWithFrame:CGRectMake(0, 100, 300, 400)];
    scrollView.backgroundColor = [UIColor blueColor];
    [self.view addSubview:scrollView];
    scrollView.contentSize = CGSizeMake(300, 500);
    
    OBView *view = [[OBView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    view.backgroundColor = [UIColor cyanColor];
    [scrollView addSubview:view];
    //    [view layoutIfNeeded];
}

//打印 如果添加[view setNeedsLayout];打印结果不变
 -[ViewController viewWillAppear:]  //说明 setNeedsLayout和自然的addsubView:setFrame等方法都是标记view,等到下次loop执行
 -[OBScrollView layoutSubviews]
 -[OBView layoutSubviews]

// 注释放开 执行[view layoutIfNeeded];
 -[OBView layoutSubviews]  			//说明 layoutIfNeeded会在当前loop中执行
 -[ViewController viewWillAppear:]
 -[OBScrollView layoutSubviews]

单独使用

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//        [view setNeedsLayout]; //单独使用,不管rect变不变化,都会调用 layout
        [view layoutIfNeeded]; //单独使用rect没有变化,则不会调用layoutSubviews
    });

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, 结束系统的绘制流程;

img

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也不执行,除非显示调用可以执行
异步绘制不行可以异步绘制
QCustomPlot 是一个强大的 Qt 库,用于创建自定义图表和数据可视化。为了支持异步绘制,它提供了一种灵活的方式来处理后台计算和更新绘图操作,避免阻塞用户界面。以下是使用 QCustomPlot 实现异步绘制的基本步骤: 1. **信号与槽机制**:利用 QCustomPlot 的内置信号(如 `plotItemsChanged` 或 `rangeChanged`)和槽函数,当数据更改或需要更新时发出信号。这可以在后台线程中完成计算。 ```cpp connect(plot, &QCustPlot::plotItemsChanged, this, [this](const QVector<int>&) { // 在后台线程执行计算并更新数据 }); ``` 2. **使用 QThread**:在后台线程(通常是 QThread 类的一个实例)中执行耗时操作,并确保在更改完成后更新图表。可以使用 `QMetaObject::invokeMethod` 来安全地将方法调用从主线程发送到绘图线程。 ```cpp void backgroundTask() { // 在这里做耗时计算 emit dataReady(); } void updateChart() { // 接收后台线程的数据更新信号 connect(this, &YourClass::dataReady, this, [this]() { // 在主线程更新图表 plot->update(); }); } ``` 3. **线程同步**:使用 `QMutex` 或 `QWaitCondition` 等工具管理主线程和绘图线程之间的同步,以防止数据混乱和界面闪烁。 4. **绘制委托**:如果需要更复杂的数据处理和绘制,可以创建绘图委托(`QCPDrawingItem` 子类),它们可以在后台线程中渲染然后传递给主线程显示。 异步绘制的优点包括提高用户体验、避免 UI 延迟以及更好地处理大量数据。记得遵循 Qt 的线程安全规则,以确保正确的线程上下文和内存管理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值