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

Tar*_*rfa 105 iphone xcode uiview

谁能给上之间的关系明确的解释UIView's setNeedsLayout,layoutIfNeededlayoutSubviews方法呢?以及将使用所有三个的示例实现.谢谢.

让我感到困惑的是,如果我向自定义视图发送一条setNeedsLayout消息,它会在此方法之后调用它layoutSubviews,然后跳过它layoutIfNeeded.从文档我预计流量为setNeedsLayout>导致layoutIfNeeded被称为>使layoutSubviews被调用.

n8g*_*ray 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.
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 谢谢 - 很高兴有人终于回答了这个问题.与此同时,我还必须深入研究setNeedsDisplay - 这会导致调用drawRect - 和contentMode.只有contentMode = UIViewContentModeRedraw才会在视图边界发生变化时调用setNeedsDisplay.其他contentMode选项仅导致视图缩放,移动一定量或与边缘对齐.我的自定义UITableViewCell不想重新定位方向更改,它只会扩展,直到我将contentMode设置为UIViewContentModeRedraw. (2认同)

que*_*dam 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); 
}
Run Code Online (Sandbox Code Playgroud)

如果您希望能够在更改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
}
Run Code Online (Sandbox Code Playgroud)

这样,如果执行以下操作,如果更改动画块内的edgeInsets属性,则将动画化contentView的帧更改.

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

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

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

  • 或者你应该能够在设置边缘插入后在动画块中调用`[self layoutIfNeeded]`. (4认同)