iOS View布局重绘机制相关方法

布局

- (void)layoutSubviews 
- (void)layoutIfNeeded
- (void)setNeedsLayout

——————————————————————————————

重绘

- (void)drawRect

- (void)setNeedsDisplay

-  (void)setNeedsDisplayInRect:(CGRect)invalidRect

——————————————————————————————

- (CGSize)sizeThatFits:(CGSize)size
- (void)sizeToFit

 

一、布局

刷新子控件的布局

-layoutSubviews方法: 这个方法不能直接调用这个方法 ,由系统调用,默认没有做任何事情,一般进行重写调用super 之后可以在这里设置子控件的frame
-setNeedsLayout方法: 标记上一个需要被重新布局标记,异步调用layoutIfNeeded刷新布局,不立即刷新,但layoutSubviews一定会被调用
-layoutIfNeeded方法:如果有需要刷新的标记,立即调用layoutSubviews进行布局(如果没有标记,不会调用layoutSubviews)

- (void)layoutSubviews;

这个方法不能直接调用这个方法 ,由系统调用,默认没有做任何事情,一般进行重写调用super 之后可以在这里设置子控件的frame 

以下情况会触发系统调用 layoutSubviews方法

  • 1、init初始化不会触发layoutSubviews
  • 2、addSubview会触发layoutSubviews
  • 3、设置view的Frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化
  • 4、滚动一个UIScrollView会触发layoutSubviews
  • 5、旋转Screen会触发父UIView上的layoutSubviews事件
  • 6、改变一个UIView大小的时候也会触发其父类的UIView上的layoutSubviews事件
  • 7、直接调用setLayoutSubviews 或者 layoutIfNeeded
- (void)layoutSubviews{
[super layoutSubviews];
//可以在这里设置子控件的frame等 }

layoutSubviews官方讲解

Lays out subviews.

The default implementation of this method does nothing on iOS 5.1 and earlier. Otherwise, the default implementation uses any constraints you have set to determine the size and position of any subviews.

在iOS5.1或之前的版本中,这个方法什么也没干.这个方法的默认实现是用参数来设定subviews的尺寸和位置的.

Subclasses can override this method as needed to perform more precise layout of their subviews. You should override this method only if the autoresizing and constraint-based behaviors of the subviews do not offer the behavior you want. You can use your implementation to set the frame rectangles of your subviews directly.

如果你需要更加精确的布局,可以在子类里面重写这个方法.仅仅在以下情况下:自动布局达不到你想要效果时你才有必要重写这个方法.你可以直接设置subviews的尺寸.

You should not call this method directly. If you want to force a layout update, call the setNeedsLayout method instead to do so prior to the next drawing update. If you want to update the layout of your views immediately, call the layoutIfNeeded method.

你不能直接调用这个方法.这个方法是系统调用的 如果你需要强制layout刷新,调用setNeedsLayout来代替.如果你想要立即刷新你的view,调用layoutIfNeeded

调用-setNeedsLayout方法

就是我们主动为这个视图设置一个标记(flag)标记为需要重新布局,告诉系统这个视图再下一个时机到来时要重新渲染.,异步调用layoutIfNeeded刷新布局,(不立即刷新,因为有标记所以 layoutSubviews一定会被调用)

在receiver标上一个需要被重新布局的标记,在系统runloop的下一个周期自动调用layoutSubviews

调用-layoutIfNeeded方法

告诉系统,如果设置了flag那么不用等待时机到来了,直接渲染吧 .立即调用layoutSubviews进行布局(如果没有标记,不会调用layoutSubviews)

结合上面的细细品味下面的内容总结

在我们没有任何干预的情况下,一个view的frame或bounds发生变化时,系统会设置一个flag给这个view,当下一个渲染时机到来时系统会重新按新的布局来渲染视图(系统会调用layoutSubViews)。setNeedLayout就是我们主动为这个视图设置一个flag,告诉系统这个视图再下一个时机到来时要重新渲染,而layoutIfNeed则是告诉系统,如果设置了flag那么不用等待时机到来了,直接渲染吧。而layoutSubviews这个方法是系统调用的,我们不需要主动调用,我们只需要调用layoutIfNeed就可以了,让系统判断是否在当前时机下立即渲染。

⚠️值得注意的是:设置标记的时机

  • 1、一个view的frame或bounds发生变化时,系统会设置一个flag给这个view
  • 2、主动调用setNeedLayout设置标记
  • 3、xib 中的约束值变了系统也会设置flag  self.blueViewW.constant = 80;  系统底层会转换为frame 所以会设置一个flag

二、重绘

-drawRect:(CGRect)rect方法:不能直接调用这个方法 ,由系统调用,重写此方法,执行重绘任务
-setNeedsDisplay方法:标上一个需要被重新绘图的标记,异步调用drawRect
-setNeedsDisplayInRect:(CGRect)invalidRect方法:标记为需要局部重绘

- (void)drawRect:(CGRect)rect;

不能直接调用这个方法 ,由系统调用,重写此方法,执行重绘任务

 以下情况会触发系统调用 drawRect方法

  • 1、如果在UIView初始化时没有设置rect大小,将直接导致drawRect不被自动调用。drawRect 调用是在Controller->loadView, Controller->viewDidLoad两方法之后掉用的.所以不用担心在控制器中,这些View的drawRect就开始画了.这样可以在控制器中设置一些值给View(如果这些View?draw的时候需要用到某些变量 值).
  • 2、该方法在调用sizeToFit后被调用,所以可以先调用sizeToFit计算出size。然后系统自动调用drawRect:方法。
  • 3、通过设置contentMode属性值为UIViewContentModeRedraw。那么将在每次设置或更改frame的时候自动调用drawRect:。
  • 4、直接调用setNeedsDisplay,或者setNeedsDisplayInRect:触发drawRect:,但是有个前提条件是rect不能为0。

drawRect方法使用注意点:

  • 1、 若使用UIView绘图,只能在drawRect:方法中获取相应的contextRef并绘图。如果在其他方法中获取将获取到一个invalidate 的ref并且不能用于画图。drawRect:方法不能手动显示调用,必须通过调用setNeedsDisplay 或 者 setNeedsDisplayInRect,让系统自动调该方法。
  • 2、若使用calayer绘图,只能在drawInContext: 中(类似鱼drawRect)绘制,或者在delegate中的相应方法绘制。同样也是调用setNeedDisplay等间接调用以上方法
  • 3、若要实时画图,不能使用gestureRecognizer,只能使用touchbegan等方法来掉用setNeedsDisplay实时刷新屏幕

-setNeedsDisplay

在receiver标上一个需要被重新绘图的标记,在下一个draw周期自动重绘,iphone device的刷新频率是60hz,也就是1/60秒后重绘

-setNeedsDisplayInRect:(CGRect)invalidRect:

标记为需要局部重绘

⚠️注意

layoutSubviews对subviews重新布局

layoutSubviews方法调用先于drawRect

setNeedsLayout在receiver标上一个需要被重新布局的标记,在系统runloop的下一个周期自动调用layoutSubviews

layoutIfNeeded方法如其名,UIKit会判断该receiver是否需要layout.根据Apple官方文档,layoutIfNeeded方法应该是这样的

layoutIfNeeded遍历的不是superview链,应该是subviews链

drawRect是对receiver的重绘,能获得context

setNeedDisplay/setNeedsDisplayInRect 在receiver标上一个需要被重新绘图的标记,在下一个draw周期自动重绘,iphone device的刷新频率是60hz,也就是1/60秒后重绘

- (void)sizeToFit; //手动调用,不能重写,系统会根据内容的帮我布局一个它认为最合适的 最优的 size 而且会改变自己的size

- (CGSize)sizeThatFits:(CGSize)size; //手动调用,可以在子类中重写,会计算出最优的 size 并返回 不会改变自己的size

- (CGSize)sizeThatFits:(CGSize)size;     // return 'best' size to fit given size. does not actually resize view. Default is return existing view size

- (void)sizeToFit;                       // calls sizeThatFits: with current view bounds and changes bounds size.

- (void)sizeToFit

  • sizeToFit会自动调用sizeThatFits方法;
  • sizeToFit不应该在子类中被重写,应该重写sizeThatFits
  • 调用sizeToFit 系统会根据内容的帮我布局一个它认为最合适的 最优的 size 而且会改变当前view自己的size

sizeToFit应用场合

另外sizeToFit声明在UIView中,所以我们的所有的视图控件,都可以调用这个方法,但是实际的开发中,我们好像也不经常使用它,来做一些布局什么的?

原因:我们一般在不方便手动布局的时候才调用sizeToFit方法,以下场合不适合手动布局可以用sizeToFit:

  • 1. navigationBar中对navigationItem的设置,(添加两个视图以上的控件到Item)
  • 2. toolBar中的对UIBarButtonItem的设置(一般我们还要添加弹簧控件)
    上述两种场合就可以用sizeToFit这个方法,来让系统给我们做自动布局。(注意:如果就添加一个控件的话,我们直接设置frame也是可以的)
  • 3.在tabBar中我们不能手动的添加的子控件,因为tabBar是根据控制器系统默认自动添加的tabBarItem。(猜想系统可能也会自动调用了这个方法)
  • 4.UILabel中添加文字,然后让调整label的大小来适应文字,我们也调用sizeToFit的方法。

- (CGSize)sizeThatFits:(CGSize)size;

返回最合适的大小,实际上不调整视图(即不会改变自己的size)。默认值是返回现有的视图大小

以上两个放的应用:使用这两个方法之前,必须要给uilabel赋值,否则不会显示内容的。

UILabel *testLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 50, 0, 0)];  

    testLabel.backgroundColor = [UIColor whiteColor];  

    testLabel.text = @"我们都有一个家啊,名字叫中国,家里攀着两条龙";  

    testLabel.font = [UIFont systemFontOfSize:20];  

    testLabel.textColor = [UIColor blackColor];  

    [testLabel sizeThatFits:CGSizeMake(20, 20)];//会计算出最优的 size 并返回 不会改变label 的size

    NSLog(@"testLabel sizeThatFits frame = %@", NSStringFromCGRect(testLabel.frame));  

    NSLog(@"best size = %@",NSStringFromCGSize([testLabel sizeThatFits:CGSizeMake(20, 20)]));  

    [testLabel sizeToFit];//会计算出最优的 size 而且会改变自己的size  内部会调用sizeThatFits去计算

    NSLog(@"testLabel sizeToFit frame = %@",NSStringFromCGRect(testLabel.frame));  

    [self.view  addSubview:testLabel];  

layoutIfNeeded方法的应用:

1. NSLayoutConstraint动画 需要在 对NSLayoutConstraint的对象赋值之后调用layoutIfNeeded方法
 
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
self.blueViewW.constant = 80; //frame /bound 发生改变(系统会对布局进行渲染 但是如果想做动画的话 需要如下自己强制执行渲染) [UIView animateWithDuration:2.0 animations:^{
//对布局进行渲染
[self.view layoutIfNeeded]; //layoutIfNeeded方法只会刷新子控件,因此要使用必须通过它的父类 }]; }

2.tableView cell 的高度的获取

 

iOS - 布局重绘机制相关方法的研究的更多相关文章

  1. iOS之 重绘机制

    最近在看Core Animation , 今天来谈谈CALayer 和 UIView 中的重绘的一些认识: 我们都知道UIView里面有个成员layer,利用这个这个layer我们可以设置一些圆角,阴 ...

  2. android控件拖动,移动、解决父布局重绘时控件回到原点

    这是主要代码: 保证其params发生改变,相对于父布局的位置就能达到位置移动到原来的位置 // 每次移动都要设置其layout,不然由于父布局可能嵌套listview,当父布局发生改变冲毁(如下拉刷 ...

  3. Phone重绘机制drawRect 转

    Phone重绘机制drawRect 如何使用iPhone进行绘图.重绘操作iPhone的绘图操作是在UIView类的drawRect方法中完成的,所以如果我们要想在一个UIView中绘图,需要写一个扩 ...

  4. iOS重绘机制drawRect

    iOS的绘图操作是在UIView类的drawRect方法中完成的,所以如果我们要想在一个UIView中绘图,需要写一个扩展UIView 的类,并重写drawRect方法,在这里进行绘图操作,程序会自动 ...

  5. iPhone重绘机制drawRect

    如何使用iPhone进行绘图.重绘操作iPhone的绘图操作是在UIView类的drawRect方法中完成的,所以如果我们要想在一个UIView中绘图,需要写一个扩展UIView 的类,并重写draw ...

  6. Qt重绘机制

    一.引发重绘的事件 1.调用repaint() 2.调用uodate() 二.控件hide或者show 三.其他 ps: repaint函数是立即重绘,没有优化 update会优化,异步重绘,所以如果 ...

  7. iOS之UI--Quartz2D的入门应用--重绘下载圆形进度条

    *:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...

  8. 浏览器渲染详细过程:重绘、重排和 composite 只是冰山一角

    https://juejin.im/entry/590801780ce46300617c89b8 渲染 这张很经典的图许多人都看过,其中的概念大家应该都很熟悉,也就是这么几个步骤:js修改dom结构或 ...

  9. 【Android】利用自己定义View的重绘实现拖动移动,获取组件的尺寸

    以下利用一个app来说明怎样利用自己定义View的重绘实现拖动移动.获取组件的尺寸. 例如以下图,触摸拖动,或者轻轻点击屏幕都能移动图片.假设碰到文字,则会弹出提示. 这里是利用自己定义View的重绘 ...

随机推荐

  1. RocketMQ集群搭建

    1.RocketMQ介绍 1.1. 简介 RocketMQ 是一款分布式.队列模型的消息中间件,具有以下特点: 能够保证严格的消息顺序 提供丰富的消息拉取模式 高效的订阅者水平扩展能力 实时的消息订阅 ...

  2. Winform快速导出

    public static void ExportExcel(DataGridView DataGridView01){ Stream stream = null; StreamWriter writ ...

  3. 【WPF】给UserControl引入多个资源

    问题:为了方便资源的复用,我们通常会把资源单独抽取为一个资源文件,供其他文件引用.而用户自定义控件UserControl中经常需要引入多个资源文件.而在XAML中由于标签UserControl.Res ...

  4. socket编程函数

    连接 TCP/IP协议规定网络数据传输应采用大端字节序 socket地址 struct sockaddr{ unsigned short sa_family; char sa_data[14]; }; ...

  5. 全栈设计模式套餐MVVM, RESTful, MVC的历史探索

    众所周知, 软件开发时遵守一个规范的设计模式非常重要, 学习行业内主流的design pattern往往能够为你节省大部分时间. 根据我2年的全栈经验, 在Web应用程序领域最流行的, 并且若干年内不 ...

  6. 关于winform窗体关闭时弹出提示框,选择否时窗体也关闭的问题

    在窗体中有FormClosing这个事件,这个事件是在窗体关闭时候运行的.如果要取消某个事件的操作,那么就在该事件中写上e.Cancel=true就能取消该事件,也就是不执行该事件.所以,你要在窗体关 ...

  7. PHP注释的艺术——phpDoc规范

    用过IDE或看过其他源码的小伙伴们应该都见过类似下面这样的注释   /** * 递归获取所有游戏分类 * @param int $id * @return array */ 看得多了就大概知道了一些规 ...

  8. skynet1.0阅读笔记_skynet的启动

    首先看skynet的启动,函数入口在 skynet_main.c 的main(),其中最重要的是: skynet_start(&config); 在skynet_start中做了两个启动: / ...

  9. kettle的jndi的使用

    kettle可以使用jdbc的方式设置job或者tansform的数据库连接,但是,同时它也支持JNDI方式连接数据库,后者更加方便,只需要配置一份配置文件就可以了,不用每个DBConnection都 ...

  10. MYSQL的用户变量(@)和系统变量(@@)

    9.3. 用户变量 可以先在用户变量中保存值然后在以后引用它:这样可以将值从一个语句传递到另一个语句.用户变量与连接有关.也就是说,一个客户端定义的变量不能被其它客户端看到或使用.当客户端退出时,该客 ...