xcode UIView 的 setNeedsLayout、layoutIfNeeded 和 layoutSubviews 是什么关系?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2807137/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 03:23:59  来源:igfitidea点击:

What is the relationship between UIView's setNeedsLayout, layoutIfNeeded and layoutSubviews?

iphonexcodeuiview

提问by Tarfa

Can anyone give a definitive explanation on the relationship between UIView'ssetNeedsLayout, layoutIfNeededand layoutSubviewsmethods? And an example implementation where all three would be used. Thanks.

任何人都可以对UIView'ssetNeedsLayout,layoutIfNeededlayoutSubviews方法之间的关系给出明确的解释吗?以及将使用所有三个的示例实现。谢谢。

What gets me confused is that if I send my custom view a setNeedsLayoutmessage 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 layoutIfNeededto be called > causes layoutSubviewsto be called.

让我感到困惑的是,如果我向我的自定义视图发送一条setNeedsLayout消息,那么它在此方法之后调用的下一件事就是直接layoutSubviews跳过layoutIfNeeded。从文档中,我希望流程是setNeedsLayout>layoutIfNeeded被调用的原因>被调用的原因layoutSubviews

采纳答案by n8gray

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

我仍在尝试自己解决这个问题,所以请持怀疑态度,如果它包含错误,请原谅我。

setNeedsLayoutis an easy one: it just sets a flag somewhere in the UIView that marks it as needing layout. That will force layoutSubviewsto 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 autoresizesSubviewsproperty. 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属性的原因,在许多情况下您不需要显式调用它。如果已设置(默认情况下),则对视图框架的任何更改都将导致视图布局其子视图。

layoutSubviewsis the method in which you do all the interesting stuff. It's the equivalent of drawRectfor 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 layoutIfNeededisn'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:

AFAIKlayoutIfNeeded通常不会在您的子类中被覆盖。这是你注定当你想要一个视图进行布局调用一个方法现在。Apple 的实现可能如下所示:

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

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

您可以调用layoutIfNeeded一个视图来强制立即布局它(以及必要时它的超视图)。

回答by quentinadam

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

我想在n8gray的回答补充一点,在某些情况下,你需要调用setNeedsLayout之后layoutIfNeeded

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 contentViewproperty and an edgeInsetsproperty that allows to set the margins around the contentView. layoutSubviewswould 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 edgeInsetsproperty, you need to override the edgeInsetssetter as follows and call setNeedsLayoutfollowed by layoutIfNeeded:

如果您希望能够在更改edgeInsets属性时为帧更改设置动画,则需要edgeInsets按如下方式覆盖setter 并调用setNeedsLayout后跟layoutIfNeeded

- (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 方法中添加对 layoutIfNeeded 的调用,动画将无法工作,因为 layoutSubviews 将在下一个更新周期被调用,这相当于在动画块之外调用它。

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

如果您只在 setEdgeInsets 方法中调用 layoutIfNeeded,则不会发生任何事情,因为未设置 setNeedsLayout 标志。