168

我觉得根据业务逻辑UIViews,最常见的是显示/隐藏是一种相当常见的范例。UILabels我的问题是,使用 AutoLayout 响应隐藏视图的最佳方式是什么,就好像它们的框架是 0x0 一样。以下是 1-3 个功能的动态列表示例。

动态功能列表

现在我从按钮到最后一个标签有一个 10px 的顶部空间,当标签被隐藏时,它显然不会向上滑动。截至目前,我为这个约束创建了一个出口,并根据我显示的标签数量来修改常量。这显然有点棘手,因为我使用负常数值将按钮向上推到隐藏帧上。这也很糟糕,因为它不受实际布局元素的限制,只是基于其他元素的已知高度/填充的偷偷摸摸的静态计算,并且显然与 AutoLayout 的构建目的作斗争。

我显然可以根据我的动态标签创建新的约束,但是对于试图折叠一些空白来说,这是很多微观管理和很多冗长的。有更好的方法吗?更改帧大小 0,0 并让 AutoLayout 在不操纵约束的情况下完成其工作?完全删除视图?

不过老实说,仅从隐藏视图的上下文中修改常量需要一行代码和简单的计算。重新创建新的约束 constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:似乎很重。

2018 年 2 月编辑:查看 Ben 的UIStackView回答

4

13 回答 13

236

我个人对显示/隐藏视图的偏好是创建一个具有适当宽度或高度约束的 IBOutlet。

constant然后我将值更新0为隐藏,或者任何应该显示的值。

这种技术的最大优点是可以保持相对约束。例如,假设您有视图 A 和视图 B,其水平间隙为x。当视图 A 的宽度constant设置为时,0.f视图 B 将向左移动以填充该空间。

无需添加或删除约束,这是一个重量级操作。简单地更新约束constant就可以了。

于 2013-10-25T10:07:50.333 回答
99

隐藏时使用常量,再次显示时使用另一个常量的解决方案0是有效的,但如果您的内容具有灵活的大小,则不能令人满意。您需要衡量您的灵活内容并设置一个常数。这感觉不对,如果内容因服务器或 UI 事件而改变大小,就会出现问题。

我有更好的解决方案。

这个想法是在我们隐藏元素时将高度为 0 的规则设置为具有高优先级,这样它就不会占用自动布局空间。

这是你如何做到的:

1.在低优先级的界面生成器中设置宽度(或高度)为0。

设置宽度 0 规则

Interface Builder 不会因为优先级低而对冲突大喊大叫。通过暂时将优先级设置为 999 来测试高度行为(1000 禁止以编程方式变异,因此我们不会使用它)。界面构建器现在可能会大喊冲突的约束。您可以通过将相关对象的优先级设置为 900 左右来解决这些问题。

2.添加一个outlet,这样你就可以在代码中修改宽度约束的优先级:

出口连接

3.调整隐藏元素时的优先级:

cell.alertTimingView.hidden    = place.closingSoon != true
cell.alertTimingWidth.priority = place.closingSoon == true ? 250 : 999
于 2015-06-10T11:46:29.090 回答
87

UIStackView可能是iOS 9+的方式。它不仅可以处理隐藏视图,如果设置正确,它还会删除额外的间距和边距。

于 2015-10-03T14:07:37.880 回答
12

在这种情况下,我将 Author 标签的高度映射到适当的 IBOutlet:

@property (retain, nonatomic) IBOutlet NSLayoutConstraint* authorLabelHeight;

当我将约束的高度设置为 0.0f 时,我们保留“填充”,因为播放按钮的高度允许它。

cell.authorLabelHeight.constant = 0; //Hide 
cell.authorLabelHeight.constant = 44; //Show

在此处输入图像描述

于 2015-12-01T07:48:55.567 回答
10

这里有很多解决方案,但我通常的方法又不同了:)

设置两组类似于 Jorge Arimany 和 TMin 的答案的约束:

在此处输入图像描述

所有三个标记的约束都具有相同的常量值。标记为 A1 和 A2 的约束将其优先级设置为 500,而标记为 B 的约束将其优先级设置为 250(或UILayoutProperty.defaultLow在代码中)。

将约束 B 连接到 IBOutlet。然后,当您隐藏元素时,只需将约束优先级设置为高(750):

constraintB.priority = .defaultHigh

但是当元素可见时,将优先级设置回低(250):

constraintB.priority = .defaultLow

与仅更改约束 B 相比,这种方法的(不可否认的次要)优势isActive是,如果通过其他方式将瞬态元素从视图中删除,您仍然有一个工作约束。

于 2018-02-13T02:57:52.477 回答
5

子类化视图并覆​​盖func intrinsicContentSize() -> CGSizeCGSizeZero如果视图被隐藏,只需返回。

于 2015-10-20T11:42:26.287 回答
5

我刚刚发现要让 UILabel 不占用空间,您必须将其隐藏并将其文本设置为空字符串。(iOS 9)

知道这个事实/错误可以帮助一些人简化他们的布局,甚至可能是原始问题的布局,所以我想我会发布它。

于 2015-12-08T07:53:54.807 回答
4

我很惊讶没有UIKit为这种期望的行为提供更优雅的方法。想要能够做到这一点似乎是一件很平常的事情。

由于将约束连接到IBOutlets并将其常量设置为0令人讨厌(并NSLayoutConstraint在您的视图有子视图时引起警告),我决定创建一个扩展,它提供了一种简单的、有状态的方法来隐藏/显示UIView具有自动布局约束的

它只是隐藏视图并移除外部约束。当您再次显示视图时,它会重新添加约束。唯一需要注意的是,您需要为周围的视图指定灵活的故障转移约束。

编辑 此答案针对 iOS 8.4 及更低版本。在 iOS 9 中,只需使用该UIStackView方法。

于 2015-05-19T23:34:14.630 回答
4

最佳实践是,一旦一切都具有正确的布局约束,添加高度或约束,这取决于您希望周围视图如何移动并将约束连接到IBOutlet属性中。

确保您的属性是strong

在代码中,您只需将常量设置为 0 并激活它,隐藏内容,或停用它以显示内容。这比弄乱常量值并保存恢复它要好。之后别忘了打电话layoutIfNeeded

如果要隐藏的内容是分组的,最好的做法是将所有内容放入一个视图中,并将约束添加到该视图中

@property (strong, nonatomic) IBOutlet UIView *myContainer;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *myContainerHeight; //should be strong!!

-(void) showContainer
{
    self.myContainerHeight.active = NO;
    self.myContainer.hidden = NO;
    [self.view layoutIfNeeded];
}
-(void) hideContainer
{
    self.myContainerHeight.active = YES;
    self.myContainerHeight.constant = 0.0f;
    self.myContainer.hidden = YES;
    [self.view layoutIfNeeded];
}

完成设置后,您可以在 IntefaceBuilder 中通过将约束设置为 0 然后返回原始值来对其进行测试。不要忘记检查其他约束优先级,以便在隐藏时根本没有冲突。测试它的另一种方法是将其设置为 0 并将优先级设置为 0,但是,您不应忘记再次将其恢复为最高优先级。

于 2016-03-05T11:00:48.480 回答
3

我构建类别以轻松更新约束:

[myView1 hideByHeight:YES];

在这里回答: 隐藏自动布局 UIView:如何让现有的 NSLayoutConstraint 更新这个

在此处输入图像描述

于 2014-03-13T17:56:45.793 回答
2

我的首选方法与 Jorge Arimany 建议的方法非常相似。

我更喜欢创建多个约束。首先为第二个标签可见时创建约束。为按钮和第二个标签之间的约束创建一个出口(如果您使用的是 objc,请确保其强大)。此约束确定按钮和第二个标签可见时的高度。

然后创建另一个约束,指定隐藏第二个按钮时按钮和顶部标签之间的高度。创建第二个约束的出口并确保该出口具有强指针。然后取消installed选中界面生成器中的复选框,并确保第一个约束的优先级低于第二个约束的优先级。

最后,当您隐藏第二个标签时,切换.isActive这些约束的属性并调用setNeedsDisplay()

就是这样,没有幻数,没有数学,如果你有多个打开和关闭的约束,你甚至可以使用插座集合来保持它们按状态组织。(AKA 将所有隐藏的约束保留在一个 OutletCollection 中,将非隐藏的约束保留在另一个中,并且只需遍历每个集合以切换它们的 .isActive 状态)。

我知道 Ryan Romanchuk 说他不想使用多个约束,但我觉得这不是微观管理,并且比以编程方式动态创建视图和约束更简单(如果我认为这是他想要避免的'我读对了这个问题)。

我创建了一个简单的示例,希望它有用...

import UIKit

class ViewController: UIViewController {

    @IBOutlet var ToBeHiddenLabel: UILabel!


    @IBOutlet var hiddenConstraint: NSLayoutConstraint!
    @IBOutlet var notHiddenConstraint: NSLayoutConstraint!


    @IBAction func HideMiddleButton(_ sender: Any) {

        ToBeHiddenLabel.isHidden = !ToBeHiddenLabel.isHidden
        notHiddenConstraint.isActive = !notHiddenConstraint.isActive
        hiddenConstraint.isActive = !hiddenConstraint.isActive

        self.view.setNeedsDisplay()
    }
}

在此处输入图像描述

于 2017-03-21T21:33:53.467 回答
0

我也会提供我的解决方案,以提供多样性。)我认为为每个项目的宽度/高度加上间距创建一个出口是荒谬的,并且会破坏代码、可能的错误和并发症的数量。

我的方法删除所有视图(在我的情况下为 UIImageView 实例),选择需要添加回的视图,然后在循环中添加回每个视图并创建新的约束。其实很简单,请大家照着做。这是我的快速而肮脏的代码:

// remove all views
[self.twitterImageView removeFromSuperview];
[self.localQuestionImageView removeFromSuperview];

// self.recipients always has to be present
NSMutableArray *items;
items = [@[self.recipients] mutableCopy];

// optionally add the twitter image
if (self.question.sharedOnTwitter.boolValue) {
    [items addObject:self.twitterImageView];
}

// optionally add the location image
if (self.question.isLocal) {
    [items addObject:self.localQuestionImageView];
}

UIView *previousItem;
UIView *currentItem;

previousItem = items[0];
[self.contentView addSubview:previousItem];

// now loop through, add the items and the constraints
for (int i = 1; i < items.count; i++) {
    previousItem = items[i - 1];
    currentItem = items[i];

    [self.contentView addSubview:currentItem];

    [currentItem mas_remakeConstraints:^(MASConstraintMaker *make) {
        make.centerY.equalTo(previousItem.mas_centerY);
        make.right.equalTo(previousItem.mas_left).offset(-5);
    }];
}


// here I just connect the left-most UILabel to the last UIView in the list, whichever that was
previousItem = items.lastObject;

[self.userName mas_remakeConstraints:^(MASConstraintMaker *make) {
    make.right.equalTo(previousItem.mas_left);
    make.leading.equalTo(self.text.mas_leading);
    make.centerY.equalTo(self.attachmentIndicator.mas_centerY);;
}];

我得到干净、一致的布局和间距。我的代码使用 Masonry,我强烈推荐它:https ://github.com/SnapKit/Masonry

于 2015-11-02T21:13:11.790 回答
0

试试BoxView,它使动态布局简洁易读。
在您的情况下,它是:

    boxView.optItems = [
        firstLabel.boxed.useIf(isFirstLabelShown),
        secondLabel.boxed.useIf(isSecondLabelShown),
        button.boxed
    ]
于 2020-06-28T12:40:06.303 回答