LayoutSubviews,layoutIfNeeded,setNeedsLayoutトリガタイミングとルール

7376 ワード

作者:伝説のサイダー銃の住所: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
"); } @end

frameはzero

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

出力:
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
"); [self.view addSubview:view]; printf("after add
"); }

出力:
after alloc initWithFrame
after add
RXLayoutView layoutSubviews

結論1
上記の2つの例は、addSubviewを削除した場合、3行目の結果は出力されません.上記の2つの例から出力順序と結果に気づき,我々は
  • viewの初期化frameかどうかにかかわらずlayoutSubview
  • はトリガーされません.
  • 他のviewに追加された場合にのみlayoutSubviewがトリガーされ、次のviewリフレッシュ時にlayoutSubview
  • がトリガーされます.

    layoutSubviews_noneZeroFrame_changeFrame

    - (void)_test_layoutSubviews_noneZeroFrame_changeFrame
    {
        RXLayoutView *view = [[RXLayoutView alloc] initWithFrame:CGRectMake(100, 200, 100, 100)];
        printf("after alloc initWithFrame
    "); [self.view addSubview:view]; printf("after add
    "); 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
    "); view.frame = CGRectMake(100, 200, 200, 200); printf("after change frame
    "); }); }); }

    出力:
    after alloc initWithFrame
    after add
    RXLayoutView layoutSubviews
    before change frame
    after change frame
    RXLayoutView layoutSubviews
    

    注意:ここでのchangeFrameには、x,y,width,heightのいずれかの値が含まれます.
    結論2:
    フレームを変更するとlayoutSubviewsがトリガーされます(変更時にトリガーされるのではなく、次のリフレッシュサイクルでトリガーされます)

    layoutSubviews_noneZeroFrame_layoutIfNeeded

    - (void)_test_layoutSubviews_noneZeroFrame_layoutIfNeeded
    {
        RXLayoutView *view = [[RXLayoutView alloc] initWithFrame:CGRectMake(100, 200, 100, 100)];
        printf("after alloc initWithFrame
    "); [self.view addSubview:view]; printf("after add
    "); 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
    "); [view layoutIfNeeded]; printf("after layoutIfNeeded
    "); }); }); }

    出力:
    after alloc initWithFrame
    after add
    RXLayoutView layoutSubviews
    before layoutIfNeeded
    after layoutIfNeeded
    

    結論3
    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
    "); [self.view addSubview:view]; printf("after add
    "); 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
    "); view.frame = CGRectMake(100, 200, 200, 200); [view layoutIfNeeded]; printf("after change frame and layoutIfNeeded
    "); }); }); }

    出力:
    after alloc initWithFrame
    after add
    RXLayoutView layoutSubviews
    before change frame and layoutIfNeeded
    RXLayoutView layoutSubviews
    after change frame and layoutIfNeeded
    

    結論4
    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
    "); [self.view addSubview:view]; printf("after add
    "); 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
    "); [view setNeedsLayout]; printf("after setNeedsLayout
    "); }); }); }

    出力:
    after alloc initWithFrame
    after add
    RXLayoutView layoutSubviews
    before setNeedsLayout
    after setNeedsLayout
    RXLayoutView layoutSubviews
    

    結論5
    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
    "); [self.view addSubview:view]; printf("after add
    "); 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
    "); view.frame = CGRectMake(100, 200, 200, 200); printf("after change frame and before setNeedsLayout
    "); [view setNeedsLayout]; printf("after change frame and setNeedsLayout
    "); }); }); }

    しゅつりょく
    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
    

    次のテストの結果と同じです

    まとめ:

  • setNeedsLayoutが呼び出されると、frameが変化するかどうかにかかわらず、次のインタフェースリフレッシュサイクルでlayoutSubviews
  • が呼び出されます.
  • layoutIfNeededが呼び出され、frameが変化するとlayoutSubviewsが呼び出されます.そうしないとlayoutSubviews
  • は呼び出されません.
  • layoutSubviewsトリガのタイミング
  • 他のビューに自分を追加する場合、サブビューを追加するときはトリガーされず、次のリフレッシュサイクルのときに
  • が呼び出される.
  • Frameが変化するとき、そして次のリフレッシュサイクルのときに
  • が呼び出される
  • setNeedsLayoutは、次のリフレッシュサイクル時に
  • を呼び出す
  • Frameが変化するlayoutIfNeededが呼び出されると、すぐに呼び出され、現在のサイクルで
  • が呼び出されます.