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
What is the relationship between UIView's setNeedsLayout, layoutIfNeeded and layoutSubviews?
提问by Tarfa
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's
setNeedsLayout
,layoutIfNeeded
和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
跳过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.
我仍在尝试自己解决这个问题,所以请持怀疑态度,如果它包含错误,请原谅我。
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:
AFAIKlayoutIfNeeded
通常不会在您的子类中被覆盖。这是你注定当你想要一个视图进行布局调用一个方法现在。Apple 的实现可能如下所示:
-(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.
您可以调用layoutIfNeeded
一个视图来强制立即布局它(以及必要时它的超视图)。
回答by quentinadam
I would like to add on n8gray's answer that in some cases you will need to call setNeedsLayout
followed 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 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
后跟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 标志。