隐藏了UIViews的自动布局?


164

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

动态功能清单

现在,从按钮到最后一个标签,我有10px的顶部空间,当标签隐藏时,它显然不会向上滑动。到目前为止,我已经创建了此约束的出口,并根据要显示的标签数修改常数。因为我使用负常量值将按钮向上推到隐藏帧上,所以这显然有点不客气。这也很糟糕,因为它不限于实际的布局元素,仅基于已知高度/其他元素的填充进行隐蔽的静态计算,并且显然与AutoLayout的用途有所抵触。

很明显,我可以根据我的动态标签创建新的约束,但这要尝试折叠一些空白,这需要大量的微观管理和冗长的工作。有更好的方法吗?更改帧大小0,0并让AutoLayout无需约束即可操作吗?完全删除视图?

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

编辑二月2018:看到本的答案与UIStackViews


谢谢瑞安的这个问题。正如你所问的那样,我真是疯了。每次我查看有关自动布局的教程时,大多数人都说请参考raywenderlich教程网站,我发现很难做到这一点。
纳西夫2014年

Answers:


82

UIStackView这可能是iOS 9+的方法。如果设置正确,它不仅可以处理隐藏的视图,还可以消除其他间距和边距。


8
当在UITableViewCell中使用UIStackView时要当心。对我来说,一旦视图变得非常复杂,滚动就开始变得断断续续,每次滚动进/出单元格时,断断续续。在底层,StackView正在添加/删除约束,并且对于平滑滚动而言,这不一定足够有效。
Kento

7
举一个小的例子说明堆栈视图如何处理这个问题。
lostintranslation

@Kento在iOS 10上还是个问题吗?
Crashalot

3
五年后...我应该更新并将其设置为公认的答案吗?现在已经提供了三个发布周期。
Ryan Romanchuk '18

1
@RyanRomanchuk您应该,这绝对是最好的答案。谢谢本!
邓肯·卢克

234

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

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

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

无需添加或删除约束,这是一项繁重的操作。只需更新约束条件即可解决问题constant


1
究竟。只要-在此示例中-您使用水平间隙将视图B定位在视图A的右侧。我一直在使用这种技术,效果很好
Max MacLeod13年

9
@MaxMacLeod只是要确保:如果两个视图之间的间隙为x,为了使视图B从视图A位置开始,我们还必须将间隙约束常数也更改为0,对吗?
kernix 2014年

1
@kernix是,如果两个视图之间需要水平间隙约束。常数0当然意味着没有间隙。因此,通过将视图A的宽度设置为0来隐藏视图A,会使视图B向左移动,以与容器齐平。顺便说一句,感谢您的投票!
Max MacLeod

2
@MaxMacLeod感谢您的回答。我尝试使用包含其他视图的UIView进行此操作,但是当我将其高度限制设置为0时,该视图的子视图不会被隐藏。此解决方案是否应该用于包含子视图的视图?谢谢!
shaunlim 2014年

6
也将赞赏一种适用于包含其他视图的UIView的解决方案...
Mark Gibaud 2014年

96

0在隐藏状态下使用一个常量,如果再次显示该常量,则可以使用功能,但是如果内容的大小灵活,则不能令人满意。您需要衡量自己的灵活内容并设置一个常数。这感觉不对,并且如果内容由于服务器或UI事件而更改大小,则会出现问题。

我有一个更好的解决方案。

这个想法是在隐藏元素时将高度为0的规则设置为高优先级,以使其不占用自动布局空间。

这样做的方法如下:

1.在接口生成器中将宽度(或高度)设置为0,但优先级较低。

设置宽度0规则

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

2.添加一个出口,以便您可以在代码中修改宽度约束的优先级:

插座连接

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

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

@SimplGy您能告诉我这个地方是什么。closingSoon?
Niharika '17

这不是通用解决方案的一部分,@ Niharika,这只是我的业务规则(显示餐厅是否即将关闭的视图)。
SimplGy

11

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

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

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

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

在此处输入图片说明


9

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

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

在此处输入图片说明

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

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

constraintB.priority = .defaultHigh

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

constraintB.priority = .defaultLow

与仅更改isActive约束B 相比,此方法的(公认的次要优势)是,如果通过其他方法从视图中删除了过渡元素,则您仍然具有工作约束。


这样的优点是在情节提要和代码中没有重复的值,例如元素的高度/宽度。十分优雅。
罗宾·多尔蒂

谢谢!!它帮助我
PARTH巴罗特

很棒的方法,谢谢
Basil

5

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


2
这很棒。有些UI元素需要触发invalidateIntrinsicContentSize。您可以在被覆盖的区域中执行此操作setHidden
DrMickeyLauer '16

5

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

知道这个事实/错误可能有助于某些人简化其布局,甚至可能简化原始问题的布局,因此我认为应该将其发布。


1
我认为您不需要隐藏它。将其文本设置为空字符串就足够了。
罗伯VS

4

我很惊讶没有提供更优雅的方法 UIKit对于这种期望的行为,。想要做似乎很普通的事情。

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

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

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


1
这也是我的首选方法。这是对其他答案的一项重大改进,因为它不仅将视图压缩为零大小。壁球方法将带来一些问题,但支出却微不足道。这方面的一个例子是在另一个答案中隐藏项很大的空白。您可能会遇到如上所述的约束违例。
约翰

@DanielSchlaug感谢您指出这一点。我已将许可证更新为MIT。但是,每当有人实施此解决方案时,我的确需要5岁以上的人才。
艾伯特·博里

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];
}

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


1
这是因为约束和视图不一定总是会被视图层次结构保留,因此,如果停用约束并隐藏视图,则无论视图层次结构决定保留还是不保留,都仍将保留它们以再次显示它们。
Jorge Arimany '16


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()
    }
}

在此处输入图片说明


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


0

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

    boxView.optItems = [
        firstLabel.boxed.useIf(isFirstLabelShown),
        secondLabel.boxed.useIf(isSecondLabelShown),
        button.boxed
    ]
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.