如何使用 AutoLayout 根据父高度调整子视图高度而不添加/删除约束

Posted

技术标签:

【中文标题】如何使用 AutoLayout 根据父高度调整子视图高度而不添加/删除约束【英文标题】:How to resize subviews height according to the parent height using AutoLayout without adding/removing constrains 【发布时间】:2014-03-11 14:02:28 【问题描述】:

假设我有一个包含多个子视图的父视图。 使用自动布局我想将父视图的高度约束设置为 0,并且我希望父视图的所有子视图都具有与父视图相同的高度约束。

当我想隐藏视图及其所有子视图而不直接更改其约束(不添加/删除约束)时,这将极大地帮助我。

目前我对我的问题的解决方案是在隐藏父视图并且显示父视图时将所有子视图的高度约束更改为零,将所有子视图更新为其原始高度。 (对于 UILabel,我有一个很好的技巧,其中行数等于 0;我没有设置它的高度约束,而是简单地更改它的内容;当我想隐藏标签时,我只需将文本设置为空字符串 @"" ,这实际上将高度设置为 0,因为没有文本。当我想显示标签时,我只需将其内容更改为我想要显示的内容。

【问题讨论】:

为什么不将高度约束连接到 IBOutlet 并在代码中设置? 如果你想隐藏,为什么不用动画而不是挤压屏幕? @NikitaTook 我说的正是这个,但是我不想将高度约束更改为所有父子视图,而是想通过 IBOutlet 将父子的高度约束设置为 0(不设置所有子视图的高度约束) @Wain,我认为这是我维护代码的最简单方法。我正在编写一个自定义 UICollectionViewCell,它的视图很少,有时会出现,有时不会。 @Vinzzz,我尝试将高度约束设置为父视图并将其高度约束更改为 0 或其原始高度。我尝试将子视图中的顶部和底部约束添加到其父视图中,且约束低于或等于约束,并将内容拥抱和内容压缩阻力优先级降低到 1。不幸的是,我没有想出一个好的计划,除了将所有子视图的高度约束更改为 0(这是可能的,但我希望有更优雅的解决方案)。 【参考方案1】:

如果我理解您的要求正确,我认为您只需要为每个子视图设置 2 个高度限制。

第一个将是一个高度约束,在显示父视图时,您希望高度为常数,并将优先级设置为小于 1000(我一直使用 800,它似乎适用于我想要的是)。这是当 superview 的高度不为 0 时将应用的约束。

第二个高度约束的 constant >= 0,priority 为 1000。这将是在超级视图高度 = 0 时应用的约束。

编辑:要获得优先级 800 约束的视图高度,您可以:

故事板:

使用您想要的任何内容设置视图。所以对于标签,设置行数、字体大小等,输入一些文本(它不一定是用户将看到的文本 - 你的代码可能会在某处改变myLabel.text。之后标签设置完毕,点击窗口右下角的“Pin”约束按钮,高度应该会自动填充。应用约束,然后在创建约束后编辑约束以降低优先级。

代码:

设置标签的字体、行数和其他一切,就像在故事板方法中一样,然后再次添加一些虚拟文本。然后,您可以通过调用[myLabel sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)].height 来询问标签要显示您给它的虚拟文本的高度


编辑 2:

好的,这和我以前的有点不同。让我们尝试一些屏幕截图和代码片段。这些只是来自我整理的一个快速测试项目,但您应该能够将这个想法扩展到您正在从事的工作。

在情节提要中,为您的超级视图(绿色背景)和超级视图顶部的位置设置水平约束。

然后,为展开高度添加高度约束。将此约束的优先级设置为High (750)。

然后,将您的子视图添加到父视图。为子视图添加约束,就好像一切正​​常,超级视图不会发生任何事情。唯一的区别是你想默认将.hidden 设置为YES (否则如果视图开始折叠,子视图将可见并且间距会很时髦)。请注意,对于多行标签,我没有指定高度限制 - XCode 会根据我输入的文本自行确定。

最后,为超级视图添加第二个高度约束,constant = 0,优先级为 1000。

现在是一点代码:

在视图控制器的 .h 文件中,为超级视图和视图的 height = 0 约束设置 IBOutlets。

MyViewController.h

#import <UIKit/UIKit.h>

@interface MyViewController : UIViewController

    IBOutlet NSLayoutConstraint *superHeighConstraint;
    IBOutlet UIView *myCollapsibleView;


@end

为了测试,我添加了一个 UISegmentedControl,它会在超级视图是否折叠时进行切换。下面是响应选择变化的代码:

MyViewController.m

...

- (IBAction)segmentUpdated:(UISegmentedControl *)sender

    if ([sender selectedSegmentIndex] == 0)
    
        [myCollapsibleView removeConstraint:superHeighConstraint];
        for (UIView *subview in [myCollapsibleView subviews])
        
            [subview setHidden:NO];
        
    
    else if ([sender selectedSegmentIndex] == 1)
    
        [myCollapsibleView addConstraint:superHeighConstraint];
        for (UIView *subview in [myCollapsibleView subviews])
        
            [subview setHidden:YES];
        
    

使用此设置,您永远不会更改代码中的任何约束 - 您只需选择何时应用或不应用父视图的 height = 0 约束。循环通过[myCollapsibleView subviews] 可以很容易地根据需要显示/隐藏子视图,并且它是动态完成的,因此如果您稍后添加或删除子视图,您不必接触代码。

模拟器图片:

加载:

切换显示:

切换隐藏:

【讨论】:

hmm...如果我的子视图是 UILabel 并且行数为 0 怎么办?那么我不知道它的高度。 嗯,我不确定我是否理解你的问题。你的意思是标签中的行数是0?那么高度显然是 0 - 但我不认为这就是你的意思。如果您正在谈论具有 1 行和标准字体的标准 UILabel,高度为 21。如果您有多行或非标准字体,那么您可以在情节提要或代码中处理它 - 请参阅我的编辑. 在UILabel中,行数=0表示行数是动态的,可以是0-n行(当然不代表高度为0)。 啊,我没有意识到行数可以是动态的。我建议在这种情况下更深入地研究优先级 - 应该有一个自动生成的约束,根据显示内容需要多少空间来设置高度。该约束最终可能会替代我在原始答案中提到的第一个约束,但我不确定 - 我以前不必深入研究它。 我不想从代码中开始更改子视图约束;如果我愿意,那么我也有一个可行的解决方案。我的问题是是否可以仅将父视图高度约束设置为 0,这将导致所有子视图的大小缩小。【参考方案2】:

稍微不同的方法可能是只使用setHidden: 使事物根据需要出现和消失。这比以编程方式使用约束更容易实现,但有时会导致在奇怪的地方出现难看的空白。我不确定它是否适用于您的特定布局,但我认为值得一提。

【讨论】:

我尝试了该解决方案,但正如您所说,它效果不佳。【参考方案3】:

这里有很好的解决方案: Hide autolayout UIView : How to get existing NSLayoutConstraint to update this one

使用类别更新已创建的约束。

【讨论】:

分类实现在哪里?它不在github中。 @kernix 抱歉,我更新了 repo。 github.com/damienromito/UIView-UpdateAutoLayoutConstraints

以上是关于如何使用 AutoLayout 根据父高度调整子视图高度而不添加/删除约束的主要内容,如果未能解决你的问题,请参考以下文章

iOS Autolayout:调整大小父视图中的 UILabels 问题

如何使用自动布局根据子视图调整父视图的高度

AutoLayout 在地图标注中动态调整 UILabel 高度和宽度?

使用 AutoLayout 并确保父视图的边界在调整大小时适合子视图

使用 AutoLayout 调整框架大小

使用 Autolayout 根据宽度的比率设置 UIImage 高度