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


105

谁能给上之间的关系明确的解释UIView's setNeedsLayoutlayoutIfNeededlayoutSubviews方法呢?还有一个将使用所有三个示例的示例实现。谢谢。

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

Answers:


104

我仍在尝试自己解决这个问题,因此请对此表示怀疑,如果其中包含错误,请原谅我。

setNeedsLayout这是一个简单的方法:它只是在UIView中的某个位置设置了一个标记,将其标记为需要布局。layoutSubviews在下一次重绘发生之前,这将强制在视图上调用。请注意,由于该autoresizesSubviews属性,在很多情况下您不需要显式调用此方法。如果已设置(默认情况下为默认值),则对视图框架的任何更改都将导致该视图布局其子视图。

layoutSubviews是您做所有有趣的事情的方法。如果可以的话,它相当于drawRectfor layout。一个简单的例子可能是:

-(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执行一个视图以强制将其(以及必要时的其超级视图)布局。


2
谢谢-很高兴有人终于回答了这个问题。在此期间,我还必须深入研究setNeedsDisplay(这将导致drawRect被调用)和contentMode。当视图的边界更改时,只有contentMode = UIViewContentModeRedraw会导致setNeedsDisplay被调用。其他contentMode选项仅导致视图缩放,移动一定量或与边缘对齐。我的自定义UITableViewCell不想在方向更改时重绘,它只能缩放,直到将contentMode设置为UIViewContentModeRedraw为止。
塔法

我认为也许您的代码中的[self.subview1 layoutSubviews]应该替换为[self.subview1 setNeedsLayout]。因为layoutSubviews是要被覆盖的,而不是要从另一个视图调用或从另一个视图调用。框架可以决定何时调用它(通过将多个布局请求分组为一个效率调用),也可以通过在视图层次结构中的某个位置调用layoutIfNeeded间接进行调用。
塔法

更正了我上面的评论:“ ...因为layoutSubviews是要被覆盖或从自身调用,而不是要从另一个视图调用...”
Tarfa 2010年

我真的不确定[subview1 layoutSubviews]。就像一样drawRect,您不应该直接调用它。但是我不确定setNeedsLayout在布局阶段调用是否会导致视图在同一布局阶段进行布局,或者是否将视图延迟到下一个视图。很高兴看到真正了解这一切的人的答案...
n8gray 2010年

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按如下方法覆盖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
}

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

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

如果未在setEdgeInsets方法中添加对layoutIfNeeded的调用,则该动画将无法工作,因为layoutSubviews将在下一个更新周期被调用,这等同于在动画块之外调用它。

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


4
或者,您应该只需要[self layoutIfNeeded]在设置边缘插入后在动画块内调用即可。
斯科特·菲斯特
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.