UIView的setNeedsLayout,layoutIfNeeded和layoutSubviews之间有什么关系?

时间:2010-05-10 23:31:14

标签: iphone xcode uiview

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

让我感到困惑的是,如果我向自定义视图发送setNeedsLayout消息,则在此方法为layoutSubviews之后调用它,然后跳过layoutIfNeeded。从文档中我可以预期流程为setNeedsLayout>导致layoutIfNeeded被调用>导致layoutSubviews被调用。

2 个答案:

答案 0 :(得分:103)

我仍然试图自己解决这个问题,所以如果它包含错误,请怀疑并请原谅我。

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通常不会在您的子类中被覆盖。当您希望立即布置视图时,您可以调用此方法。 Apple的实现可能如下所示:

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

您可以在视图上调用layoutIfNeeded以强制它(及其必要的超视图)立即布局。

答案 1 :(得分:34)

我想补充一下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设置器,如下所示并调用setNeedsLayout,然后调用{{1 }}:

layoutIfNeeded

这样,如果执行以下操作,如果更改动画块内的edgeInsets属性,则会对contentView的帧更改进行动画处理。

- (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
}

如果你没有在setEdgeInsets方法中添加对layoutIfNeeded的调用,动画将无效,因为layoutSubviews将在下一个更新周期被调用,这相当于在动画块之外调用它。

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