NSLayoutConstraint 子视图不会自动调整大小以适应容器视图

Posted

技术标签:

【中文标题】NSLayoutConstraint 子视图不会自动调整大小以适应容器视图【英文标题】:NSLayoutConstraint subviews won't autoresize to fit container view 【发布时间】:2013-07-31 17:31:11 【问题描述】:

我有一个简单的布局,其中有一个在整个屏幕上开始的containerView。在里面,我有两个垂直的子视图——topView (green), botView (orange),它们需要相等的高度,并且应该有 containerView 高度的 50%。

启动后,我从屏幕外从底部向上滑动视图(蓝色),导致containerView 调整到较短的高度。

我期望发生的是topViewbotView 都会变短,在containerView 内保持它们的均匀高度,但实际发生的是内部视图没有调整大小,尽管有限制这样做。

因为这段代码有一些基于代码的约束,还有一些 IB 约束,here 是一个示例项目,可以看到它的实际运行情况。这是被忽略的约束:

NSLayoutConstraint *c1 = [NSLayoutConstraint constraintWithItem:self.topView
                                                      attribute:NSLayoutAttributeHeight
                                                      relatedBy:NSLayoutRelationEqual
                                                         toItem:self.containerView
                                                      attribute:NSLayoutAttributeHeight
                                                     multiplier:0.5f
                                                       constant:0.f];
[self.containerView addConstraint:c1];

和调整大小的视图代码:

- (void)resizeViews 

    __weak UAViewController *weakSelf = self;
    dispatch_async(dispatch_get_main_queue(), ^
        [UIView animateWithDuration:0.25 animations:^
            weakSelf.slideInView.frame = CGRectMake(CGRectGetMinX(weakSelf.view.bounds),
                                                    CGRectGetMaxY(weakSelf.view.bounds) - CGRectGetHeight(weakSelf.slideInView.frame),
                                                    CGRectGetWidth(weakSelf.view.bounds),
                                                    CGRectGetHeight(weakSelf.slideInView.frame));

            weakSelf.containerView.frame = CGRectMake(CGRectGetMinX(weakSelf.view.bounds),
                                                      CGRectGetMinY(weakSelf.view.bounds),
                                                      CGRectGetWidth(weakSelf.view.bounds),
                                                      CGRectGetMaxY(weakSelf.view.bounds) - CGRectGetHeight(weakSelf.slideInView.frame));

        ];
    );


我在这里做错了什么?

【问题讨论】:

对了,你为什么要在resizeViews中做dispatch_async?这没有必要(除非您从其他队列中调用它,如果您这样做我会感到惊讶)。但这没有实际意义,因为如果您正在执行自动布局,则根本不应该使用 frame 属性。修改您的约束,并让约束引擎做出适当的frame 更改。 dispatch_async 正在为此调试应用程序的计时器线程调用。 【参考方案1】:

我很惊讶您将frame 设置为视图。通常在自动布局中,您可以通过更改约束 constant 而不是通过更改 frame 在视图中滑动。

例如,假设您对容器子视图的约束是这样定义的(为简单起见,由于两个视图应该占据容器的 50%,最容易通过将两个子视图定义为相同来完成高度):

[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[topView][botView(==topView)]|" options:0 metrics:nil views:views]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[topView]|" options:0 metrics:nil views:views]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[botView]|" options:0 metrics:nil views:views]];

并且,为了简单起见,定义容器之间的关系,视图中的幻灯片与其父视图的关系如下:

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[containerView][slideInView]|" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[containerView]|" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[slideInView]|" options:0 metrics:nil views:views]];

// set the slide in view's initial height to zero

NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:slideInView
                                                                    attribute:NSLayoutAttributeHeight
                                                                    relatedBy:NSLayoutRelationEqual
                                                                       toItem:nil
                                                                    attribute:NSLayoutAttributeNotAnAttribute
                                                                   multiplier:1.0
                                                                     constant:0.0];
[self.view addConstraint:heightConstraint];

然后,当您想在“视图中滑动”中滑动时,只需对其高度的变化进行相应的动画处理即可:

heightConstraint.constant = 100;
[UIView animateWithDuration:1.0 animations:^
    [self.view layoutIfNeeded];
];

因此:

现在,您也可以使用容器高度的 x% 来执行此操作,但您无法使用 VFL 轻松执行此操作,但它应该可以正常工作。您也可以通过为其顶部约束设置动画来滑入“视图中的幻灯片”(但这很麻烦,因为您必须重新计算旋转的顶部约束,这是我不想在这个简单的模型中做的事情)。

但你明白了。只需将容器视图的高度或底部约束定义为与视图中的幻灯片相关,然后通过调整其约束为视图中的幻灯片设置动画,如果您已正确定义所有约束,它将优雅地为所有在更改幻灯片中视图的约束后为layoutIfNeeded 设置动画时,四个视图(容器、视图中的幻灯片和容器的两个子视图)正确。

【讨论】:

您说的很有道理,我已将更改应用到我的项目,但我无法复制您的屏幕截图 - “无法同时满足约束”。你能把你在这里使用的修改过的项目传回来,这样我就可以知道我哪里出错了? @coneybeare 通常无法满足约束是由于您忽略了将translatesAutoresizingMaskIntoConstraints 设置为NO 以编程方式创建的视图。我必须承认,我没有看过你的项目(有时通过别人的项目可能会很麻烦),而且从头开始这样做更容易。我刚刚创建了一个空白应用程序,并将以下代码放入视图控制器的viewDidLoad:pastie.org/8194565# 希望这能说明这个概念。 我能够使用粘贴代码复制您的结果,但我很难理解为什么在使用 IB 生成的视图时同样的事情不起作用。我在所有 5 个视图上将 translatesAutoresizingMaskIntoConstraints 设置为 NO,情节提要中没有任何约束,但继续出现错误。你能看看这个(非常简单的)示例项目吗? cloud.coneybeare.net/QZ6G 我的理论是从 IB 实例化的视图自动应用了系统定义的帧大小约束,但我没有看到阻止自动创建这些的选项。 @coneybeare 不,我不认为是这样。但是 Xcode 4.6 中的 Interface Builder 可能会令人愤怒,因为它不断尝试添加约束以消除歧义。 Xcode 5 在这方面要好得多。但我发现我通常能够在 Xcode 4.6 中与 IB 搏斗,才能做到这一点。今晚我会看看你的项目。

以上是关于NSLayoutConstraint 子视图不会自动调整大小以适应容器视图的主要内容,如果未能解决你的问题,请参考以下文章

NSLayoutConstraint - 无法将子视图框架设置为父视图边界

调整 UITableViewCell 大小的 NSLayoutConstraint 错误

NSLayoutConstraint 用于代码中未知数量的子视图

如何在 collectionView 单元格中设置 NSLayoutConstraint 常量?

NSLayoutConstraint 常量没有更新

基于子视图的自动布局高度