layoutSubviews、setNeedsLayout、layoutIfNeeded 的使用(I)

layoutSubviews

简介:
  iOS5.1和之前的版本,此方法的缺省实现不会做任何事情(实现为空),iOS5.1之后(iOS6开始)的版本,此方法的缺省实现是使用你设置在此view上面的constraints(Autolayout)去决定subviews的position和size。

  UIView的子类如果需要对其subviews进行更精确的布局,则可以重写此方法。只有在autoresizing和constraint-based behaviors of subviews不能提供我们想要的布局结果的时候,我们才应该重写此方法。可以在此方法中直接设置subviews的frame。 我们不应该直接调用此方法,而应当用setNeedsLayout、layoutIfNeeded 这两个方法。

TestView.h 文件

#import 

@interface TestView : UIView

@end

TestView.m 文件

#import "TestView.h"

@implementation TestView


- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self)
    {
        NSLog(@"initWithFrame:%@" ,NSStringFromCGRect(frame));
    }
    return self;
}

//layoutSubviews方便数据计算, 调用先于drawRect
- (void)layoutSubviews
{
    NSLog(@"-------触发了layoutSubviews方法: %@", self);
    [super layoutSubviews];
    
    UIView *innerView = [[UIView alloc] initWithFrame:CGRectMake(self.center.x/2, self.center.y/2, 100, 100)];
    innerView.backgroundColor = [UIColor cyanColor];
    
    [self addSubview:innerView];
}


- (void)drawRect:(CGRect)rect {

    NSLog(@"-----调用了drawRect 方法了");
}

@end

layoutSubviews 会调用的情况

① addSubview会触发layoutSubviews;

 TestView *test1 = [[TestView alloc] init];
 [self.view addSubview:test1];

打印:

2018-08-12 18:29:52.793801+0800 StruggleSwift[9298:3032966] -------调用了initWithFrame:{{0, 0}, {0, 0}}
2018-08-12 18:29:55.594053+0800 StruggleSwift[9298:3032966] -------触发了layoutSubviews方法: >

② 设置frame值和addSubview 会触发 layoutSubviews 和 drawRect 方法

TestView *test1 = [[TestView alloc] initWithFrame:CGRectMake(70, 70, 100, 100)];
[self.view addSubview:test1];

打印:

2018-08-12 18:52:00.897356+0800 StruggleSwift[10348:3256349] -------调用了initWithFrame:{{70, 70}, {100, 100}}
2018-08-12 18:52:00.938708+0800 StruggleSwift[10348:3256349] -------触发了layoutSubviews方法: >
2018-08-12 18:52:00.939959+0800 StruggleSwift[10348:3256349] -----调用了drawRect 方法了

效果图:


layoutSubviews、setNeedsLayout、layoutIfNeeded 的使用(I)_第1张图片
调用了layoutSubviews 和 drawRect 方法

结论:当设置了frame值后,drawRect 方法会被系统调用。

③ 调用 setNeedsLayout 方法,会调用 layoutSubviews

TestView *test1 = [TestView new];
[test1 setNeedsLayout];

[self.view addSubview:test1]; 

打印:

2018-08-12 19:07:22.155301+0800 StruggleSwift[10985:3407121] -------调用了initWithFrame:{{0, 0}, {0, 0}}
2018-08-12 19:07:22.197568+0800 StruggleSwift[10985:3407121] -------触发了layoutSubviews方法: >
2018-08-12 19:07:22.198063+0800 StruggleSwift[10985:3407121] -------触发了layoutSubviews方法: >

结论
  由打印结果看 layoutSubviews 被执行了两次,第一次是因为 setNeedsLayout ,第二次是因为 addSubview 执行。layoutSubviews 被执行的前提条件是该视图被父视图调用 addSubview 否则,不会执行layoutSubviews方法。这两次的执行是在 addSubview 方法执行完以后调用的。

原因:setNeedsLayout 在receiver标上一个需要被重新布局的标记,异步调用layoutIfNeeded刷新布局,不立即刷新。在系统runloop的下一个周期自动调用layoutSubviews。

layoutSubviews 可以处理子视图中的一些数据。

④ 同时使用 setNeedsLayout、layoutIfNeeded 后会立马调用layoutSubviews

TestView *test1 = [TestView new];
[test1 setNeedsLayout];
[test1 layoutIfNeeded];
[self.view addSubview:test1];

打印:

2018-08-12 19:14:13.678237+0800 StruggleSwift[11150:3473719] -------调用了initWithFrame:{{0, 0}, {0, 0}}
2018-08-12 19:14:23.766366+0800 StruggleSwift[11150:3473719] -------触发了layoutSubviews方法: >
2018-08-12 19:14:27.242705+0800 StruggleSwift[11150:3473719] -------触发了layoutSubviews方法: >

结论:
通过打断点发现,在执行了 setNeedsLayout、layoutIfNeeded 方法后会立马调用layoutSubviews,然后在 addSubview 后又调用了一次 layoutSubviews 方法。

-layoutIfNeeded方法:在receiver标上一个需要被重新绘图的标记,在下一个draw周期自动重绘(iphone device的刷新频率是60hz,也就是1/60秒后重绘),立即调用layoutSubviews进行布局(如果没有标记,不会调用layoutSubviews)。

  如果要立即刷新,要先调用[view setNeedsLayout],把标记设为需要布局,然后马上调用[view layoutIfNeeded],实现布局。

  因为视图在第一次显示之前,标记总是“需要刷新”的,所以可以直接调用[view layoutIfNeeded]。

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

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

⑤ 调用 setNeedsDisplay

TestView *test1 = [[TestView alloc] initWithFrame: CGRectZero];
[test1 setNeedsDisplay]; 
[self.view addSubview:test1];

打印:

2018-08-12 19:26:50.034083+0800 StruggleSwift[11791:3601469] -------调用了initWithFrame:{{0, 0}, {0, 0}}
2018-08-12 19:26:52.449600+0800 StruggleSwift[11791:3601469] -------触发了layoutSubviews方法: >

结论:
  在调用addSubview 后,系统调用了layoutSubviews 方法。

⑥ 滚动一个UIScrollView会触发layoutSubviews;

⑦ 旋转Screen会触发父UIView上的layoutSubviews事件;

⑧ 改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件;




layoutSubviews 不会调用的情况

① init初始化不会触发layoutSubviews;

TestView *test1 = [[TestView alloc] init];

打印:

2018-08-12 18:27:25.653266+0800 StruggleSwift[9172:3008950] -------调用了initWithFrame:{{0, 0}, {0, 0}}

② initWithFrame: 设置frame的值,但没有使用addSubView,也不会调用layoutSubviews;

TestView *test1 = [[TestView alloc] initWithFrame:CGRectMake(10, 10, 100, 100)];

打印:

2018-08-12 18:40:43.667931+0800 StruggleSwift[9499:3138994] -------调用了initWithFrame:{{10, 10}, {100, 100}}

你可能感兴趣的:(layoutSubviews、setNeedsLayout、layoutIfNeeded 的使用(I))