UIView的setNeedsLayout, layoutifneed和layoutSubviews之间的关系是什么?

时间:2022-03-18 11:13:51

Can anyone give a definitive explanation on the relationship between UIView's setNeedsLayout, layoutIfNeeded and layoutSubviews methods? And an example implementation where all three would be used. Thanks.

谁能对UIView的setNeedsLayout、layoutifneed和layoutSubviews方法之间的关系给出一个明确的解释吗?以及一个示例实现,其中所有三个都将被使用。谢谢。

What gets me confused is that if I send my custom view a setNeedsLayout message the very next thing it invokes after this method is layoutSubviews, skipping right over layoutIfNeeded. From the docs I would expect the flow to be setNeedsLayout > causes layoutIfNeeded to be called > causes layoutSubviews to be called.

让我感到困惑的是,如果我将我的自定义视图发送给setNeedsLayout消息,它将在此方法之后调用layoutSubviews,跳过对layoutif需要的操作。从文档中,我预计流为setNeedsLayout >会导致layoutifneed被称为>导致layoutSubviews被调用。

2 个解决方案

#1


101  

I'm still trying to figure this out myself, so take this with some skepticism and forgive me if it contains errors.

我自己还在努力搞清楚这个问题,所以带着怀疑的态度,如果它包含错误,请原谅我。

setNeedsLayout is an easy one: it just sets a flag somewhere in the UIView that marks it as needing layout. That will force layoutSubviews to be called on the view before the next redraw happens. Note that in many cases you don't need to call this explicitly, because of the autoresizesSubviews property. If that's set (which it is by default) then any change to a view's frame will cause the view to lay out its subviews.

setNeedsLayout很容易:它只是在UIView中设置了一个标记,标记为需要布局。这将迫使layoutSubviews在下一次重绘之前调用视图。注意,在许多情况下,由于autoresizesSubviews属性,您不需要显式地调用它。如果设置了(默认设置),那么对视图框架的任何更改都将导致视图列出其子视图。

layoutSubviews is the method in which you do all the interesting stuff. It's the equivalent of drawRect for layout, if you will. A trivial example might be:

layoutSubviews是处理所有有趣内容的方法。它相当于布局的drawRect,如果你愿意的话。一个微不足道的例子可能是:

-(void)layoutSubviews {
    // Child's frame is always equal to our bounds inset by 8px
    self.subview1.frame = CGRectInset(self.bounds, 8.0, 8.0);
    // It seems likely that this is incorrect:
    // [self.subview1 layoutSubviews];
    // ... and this is correct:
    [self.subview1 setNeedsLayout];
    // but I don't claim to know definitively.
}

AFAIK layoutIfNeeded isn't generally meant to be overridden in your subclass. It's a method that you're meant to call when you want a view to be laid out right now. Apple's implementation might look something like this:

AFAIK layoutifneed通常不打算在子类中被重写。它是一个方法,当你想要一个视图现在被布局时,你应该调用它。苹果的实现可能是这样的:

-(void)layoutIfNeeded {
    if (self._needsLayout) {
        UIView *sv = self.superview;
        if (sv._needsLayout) {
            [sv layoutIfNeeded];
        } else {
            [self layoutSubviews];
        }
    }
}

You would call layoutIfNeeded on a view to force it (and its superviews as necessary) to be laid out immediately.

您将调用layoutifneed的视图来强制它(以及必要的超视图)立即部署。

#2


33  

I would like to add on n8gray's answer that in some cases you will need to call setNeedsLayout followed by layoutIfNeeded.

我想补充一下n8gray的答案,在某些情况下,您需要调用setNeedsLayout,然后是layoutifneed。

Let's say for example that you wrote a custom view extending UIView, in which the positioning of subviews is complex and cannot be done with autoresizingMask or iOS6 AutoLayout. The custom positioning can be done by overriding layoutSubviews.

例如,您编写了一个扩展UIView的自定义视图,其中子视图的定位很复杂,不能使用autoresizingMask或iOS6 AutoLayout进行。可以通过覆盖layoutSubviews来实现自定义定位。

As an example, let's say that you have a custom view that has a contentView property and an edgeInsets property that allows to set the margins around the contentView. layoutSubviews would look like this:

例如,假设您有一个自定义视图,它有一个contentView属性和一个edgeInsets属性,它允许在contentView周围设置边距。layoutSubviews是这样的:

- (void) layoutSubviews {
    self.contentView.frame = CGRectMake(
        self.bounds.origin.x + self.edgeInsets.left,
        self.bounds.origin.y + self.edgeInsets.top,
        self.bounds.size.width - self.edgeInsets.left - self.edgeInsets.right,
        self.bounds.size.height - self.edgeInsets.top - self.edgeInsets.bottom); 
}

If you want to be able to animate the frame change whenever you change the edgeInsets property, you need to override the edgeInsets setter as follows and call setNeedsLayout followed by layoutIfNeeded:

如果您想要在更改edgeInsets属性时使帧更改具有动画效果,您需要重写如下所示的edgeInsets setter并调用setNeedsLayout,然后调用layoutifneed:

- (void) setEdgeInsets:(UIEdgeInsets)edgeInsets {
    _edgeInsets = edgeInsets;
    [self setNeedsLayout]; //Indicates that the view needs to be laid out 
                           //at next update or at next call of layoutIfNeeded, 
                           //whichever comes first 
    [self layoutIfNeeded]; //Calls layoutSubviews if flag is set
}

That way, if you do the following, if you change the edgeInsets property inside an animation block, the frame change of the contentView will be animated.

这样,如果您执行以下操作,如果您在动画块中更改了edgeInsets属性,那么contentView的帧更改将被动画化。

[UIView animateWithDuration:2 animations:^{
    customView.edgeInsets = UIEdgeInsetsMake(45, 17, 18, 34);
}];

If you do not add the call to layoutIfNeeded in the setEdgeInsets method, the animation won't work because the layoutSubviews will get called at the next update cycle, which equates to calling it outside of the animation block.

如果不添加对setEdgeInsets方法中需要的layoutifrequired的调用,那么动画将无法工作,因为layoutSubviews将在下一次更新周期中被调用,这相当于在动画块之外调用它。

If you only call layoutIfNeeded in the setEdgeInsets method, nothing will happen as the setNeedsLayout flag is not set.

如果您只调用setEdgeInsets方法中需要的layoutifneed,那么不会发生任何事情,因为没有设置setNeedsLayout标志。

#1


101  

I'm still trying to figure this out myself, so take this with some skepticism and forgive me if it contains errors.

我自己还在努力搞清楚这个问题,所以带着怀疑的态度,如果它包含错误,请原谅我。

setNeedsLayout is an easy one: it just sets a flag somewhere in the UIView that marks it as needing layout. That will force layoutSubviews to be called on the view before the next redraw happens. Note that in many cases you don't need to call this explicitly, because of the autoresizesSubviews property. If that's set (which it is by default) then any change to a view's frame will cause the view to lay out its subviews.

setNeedsLayout很容易:它只是在UIView中设置了一个标记,标记为需要布局。这将迫使layoutSubviews在下一次重绘之前调用视图。注意,在许多情况下,由于autoresizesSubviews属性,您不需要显式地调用它。如果设置了(默认设置),那么对视图框架的任何更改都将导致视图列出其子视图。

layoutSubviews is the method in which you do all the interesting stuff. It's the equivalent of drawRect for layout, if you will. A trivial example might be:

layoutSubviews是处理所有有趣内容的方法。它相当于布局的drawRect,如果你愿意的话。一个微不足道的例子可能是:

-(void)layoutSubviews {
    // Child's frame is always equal to our bounds inset by 8px
    self.subview1.frame = CGRectInset(self.bounds, 8.0, 8.0);
    // It seems likely that this is incorrect:
    // [self.subview1 layoutSubviews];
    // ... and this is correct:
    [self.subview1 setNeedsLayout];
    // but I don't claim to know definitively.
}

AFAIK layoutIfNeeded isn't generally meant to be overridden in your subclass. It's a method that you're meant to call when you want a view to be laid out right now. Apple's implementation might look something like this:

AFAIK layoutifneed通常不打算在子类中被重写。它是一个方法,当你想要一个视图现在被布局时,你应该调用它。苹果的实现可能是这样的:

-(void)layoutIfNeeded {
    if (self._needsLayout) {
        UIView *sv = self.superview;
        if (sv._needsLayout) {
            [sv layoutIfNeeded];
        } else {
            [self layoutSubviews];
        }
    }
}

You would call layoutIfNeeded on a view to force it (and its superviews as necessary) to be laid out immediately.

您将调用layoutifneed的视图来强制它(以及必要的超视图)立即部署。

#2


33  

I would like to add on n8gray's answer that in some cases you will need to call setNeedsLayout followed by layoutIfNeeded.

我想补充一下n8gray的答案,在某些情况下,您需要调用setNeedsLayout,然后是layoutifneed。

Let's say for example that you wrote a custom view extending UIView, in which the positioning of subviews is complex and cannot be done with autoresizingMask or iOS6 AutoLayout. The custom positioning can be done by overriding layoutSubviews.

例如,您编写了一个扩展UIView的自定义视图,其中子视图的定位很复杂,不能使用autoresizingMask或iOS6 AutoLayout进行。可以通过覆盖layoutSubviews来实现自定义定位。

As an example, let's say that you have a custom view that has a contentView property and an edgeInsets property that allows to set the margins around the contentView. layoutSubviews would look like this:

例如,假设您有一个自定义视图,它有一个contentView属性和一个edgeInsets属性,它允许在contentView周围设置边距。layoutSubviews是这样的:

- (void) layoutSubviews {
    self.contentView.frame = CGRectMake(
        self.bounds.origin.x + self.edgeInsets.left,
        self.bounds.origin.y + self.edgeInsets.top,
        self.bounds.size.width - self.edgeInsets.left - self.edgeInsets.right,
        self.bounds.size.height - self.edgeInsets.top - self.edgeInsets.bottom); 
}

If you want to be able to animate the frame change whenever you change the edgeInsets property, you need to override the edgeInsets setter as follows and call setNeedsLayout followed by layoutIfNeeded:

如果您想要在更改edgeInsets属性时使帧更改具有动画效果,您需要重写如下所示的edgeInsets setter并调用setNeedsLayout,然后调用layoutifneed:

- (void) setEdgeInsets:(UIEdgeInsets)edgeInsets {
    _edgeInsets = edgeInsets;
    [self setNeedsLayout]; //Indicates that the view needs to be laid out 
                           //at next update or at next call of layoutIfNeeded, 
                           //whichever comes first 
    [self layoutIfNeeded]; //Calls layoutSubviews if flag is set
}

That way, if you do the following, if you change the edgeInsets property inside an animation block, the frame change of the contentView will be animated.

这样,如果您执行以下操作,如果您在动画块中更改了edgeInsets属性,那么contentView的帧更改将被动画化。

[UIView animateWithDuration:2 animations:^{
    customView.edgeInsets = UIEdgeInsetsMake(45, 17, 18, 34);
}];

If you do not add the call to layoutIfNeeded in the setEdgeInsets method, the animation won't work because the layoutSubviews will get called at the next update cycle, which equates to calling it outside of the animation block.

如果不添加对setEdgeInsets方法中需要的layoutifrequired的调用,那么动画将无法工作,因为layoutSubviews将在下一次更新周期中被调用,这相当于在动画块之外调用它。

If you only call layoutIfNeeded in the setEdgeInsets method, nothing will happen as the setNeedsLayout flag is not set.

如果您只调用setEdgeInsets方法中需要的layoutifneed,那么不会发生任何事情,因为没有设置setNeedsLayout标志。