隐藏UIViews的AutoLayout?

时间:2013-10-24 08:52:20

标签: ios objective-c autolayout

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

Dynamic features list

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

我显然可以根据我的动态标签创建新的约束,但这需要大量的微观管理以及尝试折叠某些空白的大量冗长。有更好的方法吗?更改帧大小0,0并让AutoLayout在没有约束操作的情况下完成它的工作?完全删除视图?

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

编辑2018年2月:请参阅Ben对UIStackView的回答

13 个答案:

答案 0 :(得分:231)

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

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

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

无需添加或删除约束,这是一项重量级操作。只需更新约束的constant即可。

答案 1 :(得分:95)

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

我有更好的解决方案。

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

这是你如何做到的:

<强> 1。在界面构建器中以低优先级设置宽度(或高度)为0。

setting width 0 rule

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

<强> 2。添加插座,以便您可以在代码中修改宽度约束的优先级:

outlet connection

第3。隐藏元素时调整优先级:

cell.alertTimingView.hidden    = place.closingSoon != true
cell.alertTimingWidth.priority = place.closingSoon == true ? 250 : 999

答案 2 :(得分:78)

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

答案 3 :(得分:11)

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

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

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

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

enter image description here

答案 4 :(得分:5)

对视图进行子类化并覆盖func intrinsicContentSize() -> CGSize。如果视图被隐藏,只需返回CGSizeZero

答案 5 :(得分:4)

我很惊讶UIKit没有为这种理想行为提供更优雅的方法。这似乎是一件非常普遍的事情。

由于将约束连接到IBOutlets并将其常量设置为0感觉很难吃(并且当您的视图有子视图时导致NSLayoutConstraint警告),我决定创建一个扩展,提供{ {3}}

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

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

答案 6 :(得分:4)

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

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

enter image description here

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

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

constraintB.priority = .defaultHigh

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

constraintB.priority = .defaultLow

对于这种方法而言,仅仅改变约束B的isActive的优点是,如果瞬态元素通过其他方式从视图中删除,你仍然有一个工作约束。

答案 7 :(得分:3)

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

[myView1 hideByHeight:YES];

在这里回答: Hide autolayout UIView : How to get existing NSLayoutConstraint to update this one

enter image description here

答案 8 :(得分:3)

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

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

答案 9 :(得分:3)

最佳做法是,一旦所有内容都具有正确的布局约束,请添加高度或约束,具体取决于您希望周围视图移动的方式并将约束连接到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];
}

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

答案 10 :(得分:2)

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

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

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

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

就是这样,没有魔术数字,没有数学,如果你有多个限制来打开和关闭你甚至可以使用插座集合来保持它们按州组织。 (AKA将所有隐藏的约束保留在一个OutletCollection中,将非隐藏的约束保留在另一个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()
    }
}

enter image description here

答案 11 :(得分: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

答案 12 :(得分:0)

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

    boxView.optItems = [
        firstLabel.boxed.useIf(isFirstLabelShown),
        secondLabel.boxed.useIf(isSecondLabelShown),
        button.boxed
    ]