layoutSubviews 是UIView里面的一个方法,不可以直接调用,属于被动调用的,如果要看这个方法的调用时机,只能是建一个UIView的子类,在子类里面重写layoutSubviews方法。
从其他博客上看到了它的调用时机总结,如下:
- init初始化不会触发layoutSubviews
- 当view的fram的值为0的时候,
addSubview
不会调用layoutSubviews
。 - addSubview的时候。
- 当view的frame发生改变的时候。
- 直接调用setNeedsLayout方法会调用。
- 滑动UIScrollView的时候。
- 旋转Screen会触发父UIView上的layoutSubviews事件。
- 改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件。
下面在项目里面一条一条验证下。在新建项目里面随便创建一个UIView的子类WeekView,在子类重写layoutSubviews
-(void)layoutSubviews{
[super layoutSubviews];
NSLog(@"执行的方法名:layoutSubviews");
}
在ViewController中引入头文件
#import "WeekView.h"
初始化一个WeekView对象,先不添加到控制器的view上
- (void)viewDidLoad {
[super viewDidLoad];
WeekView *weekView = [[WeekView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
}
运行一下,看输出台会发现什么也没有。说明init的时候不会触发layoutSubviews方法
然后将对象weekView添加到控制器的view上
- (void)viewDidLoad {
[super viewDidLoad];
WeekView *weekView = [[WeekView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
[self.view addSubview:weekView];
}
运行一下,看控制台,如下图:
可以看到layoutSubviews方法被执行了一次,说明 addSubview的时候会出发layoutSubviews方法
将weekView的fram设置为0
WeekView *weekView = [[WeekView alloc] initWithFrame:CGRectZero];
运行一下,发现打印台没有打印任何东西,说明当view的fram的值为0的时候,addSubview不会调用layoutSubviews
下面检验一下当view的fram发生改变的时候是否会调用layoutSubviews。给ViewController添加一个属性
@property(nonatomic,strong)WeekView * weekV;
把之前创建的对象赋值给该属性
self.weekV = weekView;
在touchesBegan的方法中改变weekV的fram
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
self.weekV.frame = CGRectMake(0, 100, 100, 100);
}
运行一下,并在屏幕上进行点击操作。控制台打印结果如下:
发现,打印结果和先前并无不同,说明fram改变了,但是没有触发layoutSubviews。但是我改变的只是fram的坐标,即x的值,没有改变大小,那下面改变大小不改变坐标试试
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
self.weekV.frame = CGRectMake(100, 100, 200, 100);
}
运行并点击屏幕,控制台打印结果如下
发现打印了两次,也就是 改变fram的大小会触发layoutSubviews,改变frame的x,y的值并不会触发
到这一步,调用时机总结中的八条,其实已经验证五条(前四条和最后一条)。
下面来看第五条直接调用setNeedsLayout方法。
setNeedsLayout方法的意思是: 将调用者标记为需要重新布局,然后会异步调用layoutIfNeeded刷新布局,既然异步就不立即刷新,但layoutSubviews一定会被调用
layoutIfNeeded的意思是:当我被调用且被调用者有需要刷新的标记,则立即调用被调用者的layoutSubviews方法进行布局,如果被调用者没有被标记,layoutSubviews是不会被调用的。
另外就是,如果要立即刷新,要先调用[view setNeedsLayout],把标记设为需要布局,然后马上调用[view layoutIfNeeded],实现刷新布局。现在验证下
在ViewController的touchesBegan方法中添加如下代码
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[self.weekV setNeedsLayout];
NSLog(@"点击了屏幕");
}
运行一下,先不点击屏幕,打印结果如下
打印结果没毛病,然后点击屏幕,结果如下
发现点击屏幕走了一遍layoutSubviews的方法。
那说明 直接调用setNeedsLayout会触发layoutSubviews方法
另外从打印结果中可以看出,layoutSubviews方法是被异步执行的,因为代码中 NSLog(@"点击了屏幕")我是写在 [self.weekV setNeedsLayout]下面的,但是打印的结果却是打印“点击了屏幕”在前,执行layoutSubviews在后。说明 直接调用setNeedsLayout,会异步执行layoutSubviews
那如果我调用setNeedsLayout后,然后立马调用layoutIfNeeded,会怎样。在touchesBegan方法中添加代码 [self.weekV layoutIfNeeded],修改如下:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[self.weekV setNeedsLayout];
[self.weekV layoutIfNeeded];
NSLog(@"点击了屏幕");
}
运行一下,然后点击屏幕,打印结果如下:
从打印的结果可以很明显的看出 在setNeedsLayout之后,直接调用layoutIfNeeded,会立即执行layoutSubviews方法,然后才执行后面的代码。
那如果我们在点击事件中,不调用setNeedsLayout方法,而是直接调用layoutIfNeeded会怎样呢?按照我们之前的分析,layoutSubviews将不会执行,验证下。
代码如下
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
// [self.weekView setNeedsLayout];
[self.weekView layoutIfNeeded];
NSLog(@"我点击了屏幕");
}
运行工程,多次点击屏幕,打印结果如下:
可以确定 仅仅是调用layoutIfNeeded不会调用layoutSubviews方法的
下面验证滑动UIScrollView是否会调用layoutSubviews
创建一个UIScrollView的子类WeekScrollView,在WeekScrollView.m文件中重写layoutSubviews方法,如下:
-(void)layoutSubviews{
NSLog(@"调用了scrollView的setNeedsLayout方法");
}
在ViewController.m中引入头文件
#import "WeekScrollView.h"
添加属性
@property(nonatomic,strong)WeekScrollView * weekScrollV;
初始化,并添加到控制器的view上
//创建滑动视图
self.weekScrollV = [[WeekScrollView alloc] initWithFrame:CGRectMake(0, 200, 250, 250)];
self.weekScrollV.contentSize = CGSizeMake(250, 500);
self.weekScrollV.backgroundColor = [UIColor blueColor];
[self.view addSubview:self.weekScrollV];
运行一下,先不滑动视图,打印结果如下
这个打印结果,没毛病,因为两次调用addSubview的方法。来看滑动视图后的结果,如下:
发现 只要滑动scrollView视图,会密集的调用其父类的layoutSubviews方法
最后验证旋转Screen是否会调用layoutSubviews。这就需要用到真机了。
先运行下项目,先不旋转手机,控制台打印如下
会发现scrollView 的layoutSubviews触发了两遍,但刚刚在模拟器上只调用一遍layoutSubviews。这个问题可描述成这样:拿真机做测试时,凡初始化UIScrollView的操作会调用两遍layoutSubviews,但是对于UIView却只调用一遍;而对于模拟器,两者都只是触发一遍layoutSubviews。这个问题先放一边。继续测试,先旋转手机试试,第一次旋转,打印结果如下
第二次旋转,打印结果如下
这样我们可以看出 屏幕旋转,UIScrollView的layoutSubviews会被触发,而且每次旋转都是触发两次layoutSubviews方法;但是UIView的layoutSubviews却不会被触发
为了验证一下是不是只有UIScrollView旋转才触发,我再创建一个UIButton的子类WeekButton,创建过程就略去了,在ViewController中初始化weekB,如下
//初始化按钮
self.weekB = [[WeekButton alloc] initWithFrame:CGRectMake(0, 450, 40, 40)];
self.weekB.backgroundColor = [UIColor brownColor];
[self.view addSubview:self.weekB];
在WeekButton.m中重写layoutSubviews方法
-(void)layoutSubviews{
NSLog(@"执行了WeekButton的layoutSubviews方法");
}
运行一下,先不旋转,结果如下
可以看到WeekScrollView和weekButton的layoutSubviews方法都运行了两次,先不管这个两次。然后旋转手机一次,运行结果如下
可以看到WeekScrollView和weekButton的layoutSubviews方法都运行了两次,但是WeekView的layoutSubviews方法没有运行
这样到这一步,我们就可以总结出两条:
旋转屏幕不触发UIView的layoutSubviews 方法,但会触发UIButton,UIScrollView等控件的layoutSubviews 方法
在真机上运行,初始化或者旋转屏幕,UIButton,UIScrollView等控件的子类会触发两次layoutSubviews 方法
问题也有两个了:
为什么是两次触发layoutSubviews 方法?
为什么旋转屏幕不触发UIView 的layoutSubviews 方法?
暂时找不到答案。以后搞懂了再来回答
后面我又试了下在真机上运行项目,改变UIScrollView的frame的大小,只运行了一次layoutSubviews。
综上再综上,layoutSubviews方法因为运行次数不靠谱,而且旋转屏幕不是所有的控件都会调用该方法,所有不适合在该方法中初始化视图,但是可以在该方法中刷新视图,尤其是对子视图进行重新布局。