【问题标题】:iOS Autolayout with UIScrollview: Why does content view of scroll view not fill the scroll view?iOS Autolayout with UIScrollview:为什么滚动视图的内容视图不填充滚动视图?
【发布时间】:2026-01-01 18:35:02
【问题描述】:

以下代码(在 viewDidLoad 中调用)会导致屏幕全红。我希望它是一个完全绿色的屏幕。为什么是红色的?我怎样才能让它全部变成绿色?

UIScrollView* scrollView = [UIScrollView new];
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
scrollView.backgroundColor = [UIColor redColor];
[self.view addSubview:scrollView];

UIView* contentView = [UIView new];
contentView.translatesAutoresizingMaskIntoConstraints = NO;
contentView.backgroundColor = [UIColor greenColor];
[scrollView addSubview:contentView];

NSDictionary* viewDict = NSDictionaryOfVariableBindings(scrollView,contentView);

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:0 metrics:0 views:viewDict]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|" options:0 metrics:0 views:viewDict]];

[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[contentView]|" options:0 metrics:0 views:viewDict]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[contentView]|" options:0 metrics:0 views:viewDict]];

【问题讨论】:

    标签: ios ios6 uiscrollview autolayout


    【解决方案1】:

    滚动视图的约束与其他视图的约束略有不同。 contentView 和它的superviewscrollView)之间的约束是针对scrollViewcontentSize,而不是针对它的frame。这可能看起来令人困惑,但实际上非常有用,这意味着您不必调整contentSize,而是contentSize 会自动调整以适应您的内容。 Technical Note TN2154 中描述了此行为。

    如果你想在屏幕上定义contentView 大小或类似的东西,你必须在contentView 和主视图之间添加一个约束,例如。诚然,这与将内容放入滚动视图是对立的,所以我可能不建议这样做,但可以这样做。


    为了说明这个概念,contentView 的大小将由其内容驱动,而不是由scrollViewbounds 驱动,请在您的contentView 中添加一个标签:

    UIScrollView* scrollView = [UIScrollView new];
    scrollView.translatesAutoresizingMaskIntoConstraints = NO;
    scrollView.backgroundColor = [UIColor redColor];
    [self.view addSubview:scrollView];
    
    UIView* contentView = [UIView new];
    contentView.translatesAutoresizingMaskIntoConstraints = NO;
    contentView.backgroundColor = [UIColor greenColor];
    [scrollView addSubview:contentView];
    
    UILabel *randomLabel = [[UILabel alloc] init];
    randomLabel.text = @"this is a test";
    randomLabel.translatesAutoresizingMaskIntoConstraints = NO;
    randomLabel.backgroundColor = [UIColor clearColor];
    [contentView addSubview:randomLabel];
    
    NSDictionary* viewDict = NSDictionaryOfVariableBindings(scrollView, contentView, randomLabel);
    
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:0 metrics:0 views:viewDict]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|" options:0 metrics:0 views:viewDict]];
    
    [scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[contentView]|" options:0 metrics:0 views:viewDict]];
    [scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[contentView]|" options:0 metrics:0 views:viewDict]];
    
    [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[randomLabel]-|" options:0 metrics:0 views:viewDict]];
    [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[randomLabel]-|" options:0 metrics:0 views:viewDict]];
    

    现在您会看到contentView(以及scrollViewcontentSize)已调整为适合具有标准边距的标签。而且因为我没有指定标签的宽度/高度,它会根据您放入该标签的文本进行调整。


    如果你想让contentView 也调整到主视图的宽度,你可以像这样重新定义你的viewDict,然后添加这些额外的约束(除了上面的所有其他约束):

    UIView *mainView = self.view;
    
    NSDictionary* viewDict = NSDictionaryOfVariableBindings(scrollView, contentView, randomLabel, mainView);
    
    [mainView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[contentView(==mainView)]" options:0 metrics:0 views:viewDict]];
    

    在滚动视图中有一个带有多行标签的known issue (bug?),如果你希望它根据文本的数量调整大小,你必须做一些花招,例如:

    dispatch_async(dispatch_get_main_queue(), ^{
        randomLabel.preferredMaxLayoutWidth = self.view.bounds.size.width;
    });
    

    【讨论】:

    • 如果文本太长而不能放在一行上,我似乎无法让标签垂直扩展。我将行数设置为 0,将 lineBreakMode 设置为 NSLineBreakByWordWrapping。我错过了什么吗?
    • 此外,我在您的答案中添加了最后一行,以将 contentView 的宽度固定到屏幕的宽度(否则 contentView 的宽度足以容纳长文本)。如果我使用高度约束明确设置标签的高度,那么它可以工作。
    • 是的,Matt 发现 a couple of work-arounds 解决了这个问题/多行标签错误。我已将其中一个添加到答案的底部。一个完全不满意的解决方案。看起来您必须在硬编码值或这种笨拙的解决方法之间做出选择。
    • 感谢您的链接。我尝试了 Matt 的变通方法,我想我更喜欢他更好地对标签进行子类化的另一种方法——看起来不那么笨拙。特别是如果您要添加多个标签,您可以将 numberOfLines 和 setTranslates... 方法以及 layoutSubviews 移到那里,以使代码更清晰。我还注意到,除非将代码放在 viewWillLayoutSubviews 中(如 Matt 建议的那样),否则我没有使用 dispatch_async 方法获得正确的标签高度,这意味着您还需要为标签创建属性。
    • @Rob 谢谢!还可以考虑链接到Apple's technical note 2154 that explains this stuff in detail
    【解决方案2】:

    我试过 Rob 的答案起初看起来不错。 但是!如果您还启用了缩放,此自动布局代码会妨碍您。 缩放时会不断调整 contentView 的大小。也就是说,如果我放大 (zoomScale > 1),我将无法滚动屏幕外的部分。

    经过几天与 Autolayout 的斗争,遗憾的是我找不到任何解决方案。最后,这甚至都没有关系(wat?好的,对不起)。最后,我只是去掉了contentView上的Autolayout(使用固定大小的contentView),然后在layoutSubviews中,我调整了self.scrollView.contentInset。 (我使用的是 Xcode5,iOS 7)

    我知道这不是这个问题的直接解决方案。但我只想指出一个简单的解决方法。它非常适合在滚动视图中居中固定大小的内容视图,只需 2 行代码!希望它可以帮助某人;)

    - (void)layoutSubviews {
        [super layoutSubviews];
    
        // move contentView to center of scrollView by changing contentInset,
        // because I CANNOT set this with AUTOLAYOUT!!!
        CGFloat topInset = (self.frame.size.height - self.contentView.frame.size.height)/2;
        self.scrollView.contentInset = UIEdgeInsetsMake(topInset, 0, 0, 0);
    }
    

    【讨论】:

      【解决方案3】:

      通常,自动布局将视图的顶部、左侧、底部和右侧边缘视为可见边缘。也就是说,如果您将视图固定到其父视图的左边缘,您实际上是将其固定到父视图边界的最小 x 值。更改超级视图的边界原点不会更改视图的位置。

      UIScrollView 类通过更改其边界的原点来滚动其内容。为了使用自动布局,滚动视图中的顶部、左侧、底部和右侧边缘现在意味着其内容视图的边缘。

      对滚动视图的子视图的约束必须导致要填充的大小,然后将其解释为滚动视图的内容大小。 (这不应与用于 Auto Layout 的 intrinsicContentSize 方法混淆。)要使用 Auto Layout 调整滚动视图的框架大小,必须明确限制滚动视图的宽度和高度,或者滚动视图的边缘必须是绑定到其子树之外的视图。

      请注意,您可以通过在视图和滚动视图的子树之外的视图(例如滚动视图的超级视图)之间创建约束,使滚动视图的子视图看起来浮动(不滚动)在其他滚动内容之上。

      这里有两个如何配置滚动​​视图的例子,首先是混合方式,然后是纯方式

      【讨论】: