UIView的setNeedsLayout,layoutIfNeeded和layoutSubviews之间的关系是什么?
任何人都可以对UIView's
setNeedsLayout
,layoutIfNeeded
和layoutSubviews
方法之间的关系给出明确的解释吗?还有一个使用全部三个的示例实现。谢谢。UIView的setNeedsLayout,layoutIfNeeded和layoutSubviews之间的关系是什么?
让我感到困惑的是,如果我发送我的自定义视图setNeedsLayout
消息,那么它在此方法之后调用的第二件事是layoutSubviews
,跳过layoutIfNeeded
。从文档我预计流程为setNeedsLayout
>原因layoutIfNeeded
被称为>原因layoutSubviews
被调用。
我仍然试图弄清楚自己,所以带着一些怀疑和原谅我,如果它包含错误。
setNeedsLayout
很简单:它只是在UIView的某个地方设置一个标志,标志着它需要布局。这将迫使layoutSubviews
在下一次重绘发生之前在视图上被调用。请注意,在许多情况下,由于autoresizesSubviews
属性,您不需要明确地调用它。如果已设置(默认情况下),则对视图框架的任何更改都会导致视图布置其子视图。
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
一般不意味着在子类中被覆盖。这是一种当您想要放置视图时打算打电话给您的方法。苹果的实现可能是这个样子:
-(void)layoutIfNeeded {
if (self._needsLayout) {
UIView *sv = self.superview;
if (sv._needsLayout) {
[sv layoutIfNeeded];
} else {
[self layoutSubviews];
}
}
}
你会打电话layoutIfNeeded
上以迫使它(以及它的superviews必要)立即布置。
我想补充一下n8gray的回答,在某些情况下,您需要拨打setNeedsLayout
然后layoutIfNeeded
。
比方说,您编写了一个扩展UIView的自定义视图,其中子视图的位置很复杂,无法使用autoresizingMask或iOS6 AutoLayout完成。定制定位可以通过覆盖layoutSubviews
来完成。
举个例子,假设您有一个自定义视图,该视图具有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);
}
如果你希望能够随时更改edgeInsets
属性动画帧的变化,你需要重写edgeInsets
setter方法如下,并呼吁其次layoutIfNeeded
setNeedsLayout
:
- (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
}
这样,如果您执行以下操作,如果更改动画块内的edgeInsets属性,则contentView的框架更改将为动画。
[UIView animateWithDuration:2 animations:^{
customView.edgeInsets = UIEdgeInsetsMake(45, 17, 18, 34);
}];
如果不添加调用layoutIfNeeded在setEdgeInsets方法,动画将无法工作,因为layoutSubviews将在下一个更新周期,这相当于调用它的动画块外调用。
如果只调用setEdgeInsets方法中的layoutIfNeeded,则不会设置setNeedsLayout标志。
或者您应该只需在设置边缘插图后在动画块内调用'[self layoutIfNeeded]''。 – 2016-09-16 22:16:12
谢谢 - 很高兴有人终于回答了这个问题。在此期间,我还必须深入研究setNeedsDisplay - 它会导致drawRect被调用 - 和contentMode。只有contentMode = UIViewContentModeRedraw将导致setNeedsDisplay在视图边界更改时被调用。其他contentMode选项只会导致视图缩放,移动一定量,或者与边缘对齐。我的自定义UITableViewCell不想重新定位方向更改,它只会缩放,直到我将contentMode设置为UIViewContentModeRedraw。 – Tarfa 2010-05-29 13:08:05
我想你的代码中的[self.subview1 layoutSubviews]应该替换为[self.subview1 setNeedsLayout]。由于layoutSubviews意味着被覆盖,但并不意味着从另一个视图调用。框架决定何时调用它(通过将多个布局请求分组为一个调用以提高效率),或者通过在视图层次结构中的某个位置调用layoutIfNeeded来间接执行此操作。 – Tarfa 2010-05-29 13:20:25
更正以上我的评论:“...由于layoutSubviews是意图被覆盖或从自己调用,但并不意味着被调用或从另一个视图...” – Tarfa 2010-05-29 13:29:01