layoutSubviews,layoutIfNeeded,setNeedsLayout触发时机和规则

作者:传说中的汽水枪
地址:https://www.jianshu.com/p/f17ac629dbc0
版权所有,欢迎转载,转载请注明出处,欢迎留言评论。

在实际的开发过程中和面试当中都会遇到这样的问题,先用相关的测试代码。

测试View RXLayoutView

@implementation RXLayoutView

- (id)init
{
    if (self = [super init]) {
        self.backgroundColor = [UIColor redColor];
    }
    return self;
}
- (id)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        self.backgroundColor = [UIColor redColor];
    }
    return self;
}

- (void)layoutSubviews
{
    printf("RXLayoutView layoutSubviews\n");
}

@end

frame 为zero

- (void)_test_layoutSubviews_zeroFrame
{
    RXLayoutView *view = [[RXLayoutView alloc] init];
    printf("after alloc init\n");
    [self.view addSubview:view];
    printf("after add\n");
}

输出:

after alloc initWithFrame
after add
RXLayoutView layoutSubviews

frame为NoneZero

- (void)_test_layoutSubviews_noneZeroFrame
{
    RXLayoutView *view = [[RXLayoutView alloc] initWithFrame:CGRectMake(100, 200, 100, 100)];
    printf("after alloc initWithFrame\n");
    [self.view addSubview:view];
    printf("after add\n");
}

输出:

after alloc initWithFrame
after add
RXLayoutView layoutSubviews
结论一

上述两个例子当把addSubview给去掉的时候,是不会输出第三行结果的。
从上述两个例子注意到输出顺序和结果,我们可以得知

  • view的初始化无论是否是frame都不会触发layoutSubviews
  • 只有当添加到其他view的时候,才会触发layoutSubviews,并且是在下一个view刷新的时候触发layoutSubviews

layoutSubviews_noneZeroFrame_changeFrame

- (void)_test_layoutSubviews_noneZeroFrame_changeFrame
{
    RXLayoutView *view = [[RXLayoutView alloc] initWithFrame:CGRectMake(100, 200, 100, 100)];
    printf("after alloc initWithFrame\n");
    [self.view addSubview:view];
    printf("after add\n");
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            printf("before change frame\n");
            view.frame = CGRectMake(100, 200, 200, 200);
            printf("after change frame\n");
        });
    });
}

输出:

after alloc initWithFrame
after add
RXLayoutView layoutSubviews
before change frame
after change frame
RXLayoutView layoutSubviews

注:这里的changeFrame包括 x,y,width,height中的任何一个值。

结论二:

变化frame的时候,触发layoutSubviews(是在下一个刷新周期触发,而不是在修改的时候触发)

layoutSubviews_noneZeroFrame_layoutIfNeeded

- (void)_test_layoutSubviews_noneZeroFrame_layoutIfNeeded
{
    RXLayoutView *view = [[RXLayoutView alloc] initWithFrame:CGRectMake(100, 200, 100, 100)];
    printf("after alloc initWithFrame\n");
    [self.view addSubview:view];
    printf("after add\n");
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            printf("before layoutIfNeeded\n");
            [view layoutIfNeeded];
            printf("after layoutIfNeeded\n");
        });
    });
}

输出:

after alloc initWithFrame
after add
RXLayoutView layoutSubviews
before layoutIfNeeded
after layoutIfNeeded
结论三

layoutIfNeeded如果frame没有变化的时候,是没有任何效果的。

layoutSubviews_noneZeroFrame_changeFrame_layoutIfNeeded

- (void)_test_layoutSubviews_noneZeroFrame_changeFrame_layoutIfNeeded
{
    RXLayoutView *view = [[RXLayoutView alloc] initWithFrame:CGRectMake(100, 200, 100, 100)];
    printf("after alloc initWithFrame\n");
    [self.view addSubview:view];
    printf("after add\n");
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            printf("before change frame and layoutIfNeeded\n");
            view.frame = CGRectMake(100, 200, 200, 200);
            [view layoutIfNeeded];
            printf("after change frame and layoutIfNeeded\n");
        });
    });
}

输出:

after alloc initWithFrame
after add
RXLayoutView layoutSubviews
before change frame and layoutIfNeeded
RXLayoutView layoutSubviews
after change frame and layoutIfNeeded
结论四

frame变化和layoutIfNeeded会立即触发layoutSubviews

layoutSubviews_noneZeroFrame_setNeedsLayout

- (void)_test_layoutSubviews_noneZeroFrame_setNeedsLayout
{
    RXLayoutView *view = [[RXLayoutView alloc] initWithFrame:CGRectMake(100, 200, 100, 100)];
    printf("after alloc initWithFrame\n");
    [self.view addSubview:view];
    printf("after add\n");
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            printf("before setNeedsLayout\n");
            [view setNeedsLayout];
            printf("after setNeedsLayout\n");
        });
    });
}

输出:

after alloc initWithFrame
after add
RXLayoutView layoutSubviews
before setNeedsLayout
after setNeedsLayout
RXLayoutView layoutSubviews
结论五

frame没有变化, setNeedsLayout是强制layoutSubviews,但是是在下一个页面刷新周期

layoutSubviews_noneZeroFrame_changeFrame_setNeedsLayout

- (void)_test_layoutSubviews_noneZeroFrame_changeFrame_setNeedsLayout
{
    RXLayoutView *view = [[RXLayoutView alloc] initWithFrame:CGRectMake(100, 200, 100, 100)];
    printf("after alloc initWithFrame\n");
    [self.view addSubview:view];
    printf("after add\n");
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            printf("before change frame and setNeedsLayout\n");
            view.frame = CGRectMake(100, 200, 200, 200);
            printf("after change frame and before setNeedsLayout\n");
            [view setNeedsLayout];
            printf("after change frame and setNeedsLayout\n");
        });
    });
}

输出

after alloc initWithFrame
after add
RXLayoutView layoutSubviews
before change frame and setNeedsLayout
after change frame and before setNeedsLayout
after change frame and setNeedsLayout
RXLayoutView layoutSubviews

跟上一个测试结果一样

总结:

  1. 当调用了setNeedsLayout,无论frame有没有变化,都会在下一个界面刷新周期的时候调用layoutSubviews
  2. 当调用layoutIfNeeded,如果frame变化了会调用layoutSubviews,否则不会调用layoutSubviews
  3. layoutSubviews 触发的时机
  • 把自己添加到其他View的时候,添加子view的时候并不会触发,并且是在下一个刷新周期的时候调用
  • Frame变化的时候,并且是在下一个刷新周期的时候调用
  • setNeedsLayout,并且是在下一个刷新周期的时候调用
  • Frame变化且调用 layoutIfNeeded,会立即调用,在当前周期调用

你可能感兴趣的:(layoutSubviews,layoutIfNeeded,setNeedsLayout触发时机和规则)