iOS绘制视图流程

目的:了解View的绘制过程,可以帮我们自定义自己的控件,解决UI界面一下问题,如刷新时机,界面卡顿等一些优化问题。

问题1:什么条件下会调用layoutSubView?

常见情况如下:

  • a. 设置View的frame的时候回触发,前提是前后设置的frame不一样。
  • b. 调用 init 方法初始化不会触发 layoutSubviews,但是是用 initWithFrame 进行初始化时,当rect的值不为CGRectZero时,也会触发。(遵守a点)
  • c. addSubView 会触发,或者改变View大小的时候也会触发。
  • d. 滑动UIScrollView的时候会触发当前View的 layoutSubVIew,与之不同的是选择手机屏幕,调用的是父View的layoutSubView。

需要注意的是:

       layoutSubView这个方法就是一个开关,起到了一个通知的作用,它的功能是告诉系统需要需要重新布局,如果现在这个方法发送通知之前完成某个功能,需要重写它即可。

       在UIView的 layoutSubViews 或者 UIViewController 的 viewDidLayoutSubviews 方法里后可以拿到view的实际frame,所以只有当上面两个方法执行完成frame才会计算出来。所以为什么在viewDidLoad 里通过setFrame的方式,修改原先在storyboard里拖动的约束控件无效。因为updateViewConstraints在viewDidLoad后执行,会覆盖掉之前的设置的frame,所以设置无效。

UI布局刷新

  • setNeedsLayout 这个方法是对布局进行标记,它不会立即刷新布局,会异步调用layoutIfNeeded 方法。

  • layoutIfNeeded 这个方法会检测当前的视图有没有通过setNeedsLayout方法设置刷新标记,如果有立即调用layoutSubviews进行布局,如果没有标记,则不会调用layoutSubviews。

由上面两个方法可知:如果要立即刷新,要先调用[view setNeedsLayout],把标记设为需要布局,然后马上调用[view layoutIfNeeded],如下所示。

[self.view setNeedsLayout];
[self.view layoutIfNeeded];

重绘视图

  • drawRect:(CGRect)rect 方法:在子类重写此方法,执行重绘任务,比如自定义自己想要的图像,并且这个方法的调用是在ViewController的loadView和viewDidLoad之后,并且该方法调用也是在sizeToFit方法执行后。苹果官方不建议直接调用drawRect:方法来绘图而是推荐使用setNeedsDisplay或者setNeedsDisplayInRect:方法,它会自动调用drawRect。

  • setNeedsDisplay 方法:标记为需要重绘,异步调用drawRect方法。

  • setNeedsDisplayInRect:(CGRect)invalidRect 方法:标记为需要局部重绘,

  • sizeToFit 方法: 会自动调用sizeThatFits方法,为了代码简洁性sizeToFit不应该在子类中被重写,应该重写sizeThatFits方法。

  • sizeThatFits:(CGSize)size 方法:返回一个适合的size,sizeToFit可以被手动直接调用。sizeToFit和sizeThatFits方法都没有递归,对subviews也不负责,只负责自己的绘制任务。

    在绘制之前需要layout,所以layoutSubviews方法调用先于drawRectsetNeedsLayout在一个需要被重新布局的标记,在系统runloop的下一个周期自动调用layoutSubviews。

注意:

 若要实时画图,不能使用gestureRecognizer,只能使用touchbegan等方法来掉用setNeedsDisplay实时刷新屏幕。

在UIView中,呈现给我们的是layer,每一个在 UIKit 中的 view 都有它自己的 CALayer。每一个layer都有一个content,这个content指向的是一块缓存,叫做backing store(后备存储),backing store有点像一个图像。这个后备存储正是被渲染到显示器上的,渲染后的结果就是我们在屏幕上看到是结果。所以绘制流程大概是分为3步:

  • a. 每一个UIView都有一个layer,每一个layer都有个content,这个content指向的是一块缓存,叫做backing store。
  • b. UIView的绘制和渲染是两个过程,当UIView被绘制时,CPU执行drawRect,通过context将数据写入backing store。渲染是由GPU完成的。
  • c. 当backing store写完后,通过render server交给GPU去渲染,将backing store中的bitmap数据显示在屏幕上。

问题2:只在drawRect方法里才能获取当前图层的上下文?

- (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context {

// 把视图图层对应上下文压入栈顶

UIGraphicsPushContext(context);    

CGRect bounds;    bounds = CGContextGetClipBoundingBox(context);

// 执行drawRect:bounds 方法
[self drawRect:bounds];

// 把上下文出栈
UIGraphicsPopContext();

}

       答:有源码可知iOS系统会维护一个CGContextRef的栈,而UIGraphicsGetCurrentContext()会取栈顶的CGContextRef,当前视图图层的上下文的入栈和出栈操作恰好将drawRect的执行包裹在其中,所以说只在drawRect方法里才能获取当前图层的上下文。

总结:

    iOS完成页面布局总是经历三个过程:测量阶段(updating constraints )--> 布局阶段(layout)--> 显示阶段(display)。

       在updating constraints阶段,在这个阶段主要是计算控件,和控件间的约束,为下一步layout做准备。在这个阶段测量是从下而上,即先测量子view然后再测量父view,重复执行这个过程直到根节点。

       在layout阶段,在这个阶段主要利用是updating constraints阶段的信息设置view的center和bounds,这个layout是从上向下,即先设置父view的center和bounds,再设置子view的,递归的重复这两个步骤,直到全部的view。

       在display阶段,通过上面阶段,手机屏幕还看不到view,只有经过display阶段,iOS系统才会把view渲染到手机屏幕上。这个阶段也是从上而下的,并且这个阶段会调用drawRect方法。

参考:https://www.jianshu.com/p/c49833c04362

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值