sizeToFit 依赖 layoutSubviews 如何实现?

How to implement sizeToFit if it depends on layoutSubviews?

layoutSubviews 的文档中,Apple 说:

You should not call this method directly.

我正在尝试实施 sizeToFit。我希望它在所有子视图上放置一个紧密的边界框。在确定这样的边界框之前,我必须布局子视图。这意味着我必须调用 layoutSubviews,Apple 对此表示不满。如何在不违反 Apple 规则的情况下解决这个难题?

- (void)layoutSubviews
{
  [super layoutSubviews];

  self.view0.frame = something;
  self.view1.frame = somethingElse;
}

- (void)sizeToFit
{
   [self layoutSubviews];

   self.frame = CGRectMake(
     self.frame.origin.x,
     self.frame.origin.y,
     MAX(
       self.view0.frame.origin.x + self.view0.frame.size.width,
       self.view1.frame.origin.x + self.view1.frame.size.width
     ),
     MAX(
       self.view0.frame.origin.y + self.view0.frame.size.height,
       self.view1.frame.origin.y + self.view1.frame.size.height
     )
  );
}

如果你想强制layoutSubviews发生,但不想直接调用它,设置needsLayout标志,然后要求它布局。

[self setNeedsLayout];
[self layoutIfNeeded];

不应覆盖 -sizeToFit。而是用视图的当前边界大小覆盖由 -sizeToFit 内部调用的 -sizeThatFits:

You should not override this method. If you want to change the default sizing information for your view, override the sizeThatFits: instead. That method performs any needed calculations and returns them to this method, which then makes the change. – UIView Class Reference


另外,即使您要覆盖 -sizeToFit,也很可能没有理由立即执行布局。您只需调整视图的大小,即设置其边界大小。这会触发对 -setNeedsLayout 的调用,将视图标记为 需要布局 。但是除非您想为视图设置动画,否则不必立即应用新布局。

这种延迟更新模式的要点在于,如果您执行多个连续更新,它可以节省大量时间,因为实际更新只执行一次。


我通常这样做。它就像一个魅力。

#pragma mark - Layout & Sizing

- (void)layoutSubviews
{
    [self calculateHeightForWidth:self.bounds.size.width applyLayout:YES];
}

- (CGSize)sizeThatFits:(CGSize)size
{
    CGFloat const width = size.width;
    CGFloat const height = [self calculateHeightForWidth:width applyLayout:NO];

    return CGSizeMake(width, height);
}

- (CGFloat)calculateHeightForWidth:(CGFloat)width applyLayout:(BOOL)apply
{
    CGRect const topViewFrame = ({
        CGRect frame = CGRectZero;
        ...
        frame;
    });

    CGRect const bottomViewFrame = ({
        CGRect frame = CGRectZero;
        ...
        frame;
    });

    if (apply) {
        self.topView.frame = topViewFrame;
        self.bottomView.frame = bottomViewFrame;
    }

    return CGRectGetMaxY(bottomViewFrame);
}

请注意,示例代码适用于可以以任何宽度显示的视图,并且容器会要求特定宽度的首选高度。

虽然可以轻松调整其他布局样式的代码。

我认为在使用自动布局时很少需要使用框架。考虑到你想在不违反Apple规则的情况下解决问题,我建议使用Auto Layout方式:

设置约束以确定容器视图的大小。

基本上,约束的设置方式使得容器的宽度和高度可以由自动布局系统确定。

例如,您可以设置如下约束:

请注意,view0 和 view1 必须同时设置宽度和高度限制。

当您在项目中的某处实例化视图时,您不再需要设置宽度和高度约束。自动布局将根据您之前设置的约束猜测其大小(称为 intrinsicContentSize)。