iOS layoutSubviews

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];
}

运行一下,看控制台,如下图:

iOS layoutSubviews_第1张图片

可以看到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);
}

运行一下,并在屏幕上进行点击操作。控制台打印结果如下:


iOS layoutSubviews_第2张图片

发现,打印结果和先前并无不同,说明fram改变了,但是没有触发layoutSubviews。但是我改变的只是fram的坐标,即x的值,没有改变大小,那下面改变大小不改变坐标试试

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    self.weekV.frame = CGRectMake(100, 100, 200, 100);
}

运行并点击屏幕,控制台打印结果如下

iOS layoutSubviews_第3张图片

发现打印了两次,也就是 改变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(@"点击了屏幕");
}

运行一下,先不点击屏幕,打印结果如下


打印结果没毛病,然后点击屏幕,结果如下
iOS layoutSubviews_第4张图片

发现点击屏幕走了一遍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(@"点击了屏幕");
}

运行一下,然后点击屏幕,打印结果如下:

iOS layoutSubviews_第5张图片

从打印的结果可以很明显的看出 在setNeedsLayout之后,直接调用layoutIfNeeded,会立即执行layoutSubviews方法,然后才执行后面的代码。
那如果我们在点击事件中,不调用setNeedsLayout方法,而是直接调用layoutIfNeeded会怎样呢?按照我们之前的分析,layoutSubviews将不会执行,验证下。
代码如下

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
//    [self.weekView setNeedsLayout];
    [self.weekView layoutIfNeeded];
    NSLog(@"我点击了屏幕");
}

运行工程,多次点击屏幕,打印结果如下:

iOS layoutSubviews_第6张图片

可以确定 仅仅是调用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];

运行一下,先不滑动视图,打印结果如下

iOS layoutSubviews_第7张图片

这个打印结果,没毛病,因为两次调用addSubview的方法。来看滑动视图后的结果,如下:

发现 只要滑动scrollView视图,会密集的调用其父类的layoutSubviews方法

最后验证旋转Screen是否会调用layoutSubviews。这就需要用到真机了。
先运行下项目,先不旋转手机,控制台打印如下

iOS layoutSubviews_第9张图片

会发现scrollView 的layoutSubviews触发了两遍,但刚刚在模拟器上只调用一遍layoutSubviews。这个问题可描述成这样:拿真机做测试时,凡初始化UIScrollView的操作会调用两遍layoutSubviews,但是对于UIView却只调用一遍;而对于模拟器,两者都只是触发一遍layoutSubviews。这个问题先放一边。继续测试,先旋转手机试试,第一次旋转,打印结果如下
iOS layoutSubviews_第10张图片

第二次旋转,打印结果如下
iOS layoutSubviews_第11张图片

这样我们可以看出 屏幕旋转,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方法");
}

运行一下,先不旋转,结果如下


iOS layoutSubviews_第12张图片

可以看到WeekScrollView和weekButton的layoutSubviews方法都运行了两次,先不管这个两次。然后旋转手机一次,运行结果如下

iOS layoutSubviews_第13张图片

可以看到WeekScrollView和weekButton的layoutSubviews方法都运行了两次,但是WeekView的layoutSubviews方法没有运行
这样到这一步,我们就可以总结出两条:
旋转屏幕不触发UIView的layoutSubviews 方法,但会触发UIButton,UIScrollView等控件的layoutSubviews 方法
在真机上运行,初始化或者旋转屏幕,UIButton,UIScrollView等控件的子类会触发两次layoutSubviews 方法
问题也有两个了:
为什么是两次触发layoutSubviews 方法?
为什么旋转屏幕不触发UIView 的layoutSubviews 方法?
暂时找不到答案。以后搞懂了再来回答

后面我又试了下在真机上运行项目,改变UIScrollView的frame的大小,只运行了一次layoutSubviews。

综上再综上,layoutSubviews方法因为运行次数不靠谱,而且旋转屏幕不是所有的控件都会调用该方法,所有不适合在该方法中初始化视图,但是可以在该方法中刷新视图,尤其是对子视图进行重新布局。

你可能感兴趣的:(iOS layoutSubviews)