当一个视图被隐藏时,如何使用自动布局来移动其他视图?

我已经在IB中设计了我的自定义细胞,子类化它,并将我的outlet连接到我的自定义类。我有三个子视图在单元格的内容是:UIView (cdView)和两个标签(titleLabel和emailLabel)。取决于每行可用的数据,有时我想在单元格中显示UIView和两个标签,有时只有两个标签。我要做的是设置约束如果我将UIView属性设置为隐藏或者我将它从superview中移除两个标签就会移到左边。我尝试将UIView的引导约束设置为10px的Superview (Cell content)和10px的UILabels引导约束到下一个视图(UIView)。在代码的后面

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(IndexPath *)indexPath {
    

// ...


Record *record = [self.records objectAtIndex:indexPath.row];
    

if ([record.imageURL is equalToString:@""]) {
cell.cdView.hidden = YES;
}
}

我把手机藏起来了。cdView和我想要标签移动到左边,但他们在细胞中保持相同的位置。我试着移除细胞。cdView从superview,但它也没有工作。我附上了图片,以澄清我是什么。

cell

我知道如何通过编程来做到这一点,我不是在寻找解决方案。我想要的是在IB中设置约束,我希望如果其他视图被删除或隐藏,我的子视图将动态移动。有可能在IB自动布局中做到这一点吗?

.....
205607 次浏览

这是可能的,但你得多做点工作。首先有几个概念性的东西需要解决:

  • 隐藏视图,即使它们不绘制,在自动布局中还参与和通常保留它们的框架,将其他相关视图留在它们的位置。
  • 当从父视图中移除一个视图时,所有相关约束也会从视图层次结构中移除。

在你的情况下,这可能意味着:

  • 如果您将左侧视图设置为隐藏,则标签保持不变,因为左侧视图仍然占用空间(即使它不可见)。
  • 如果你移除左视图,你的标签可能会被模糊地约束,因为你不再对标签的左边有约束。

你需要做的是明智地过度约束你的标签。保留现有的约束(另一个视图有10pts的空间),但添加另一个约束:使标签的左边缘距离其父视图的左边缘有10pts的优先级(默认的高优先级可能会工作得很好)。

然后,当你想让它们向左移动时,完全移除左侧视图。左视图的强制10pt约束将与它相关的视图一起消失,你将只剩下一个高优先级约束,即标签距离它们的父视图10pt。在接下来的布局中,这应该会导致它们向左扩展,直到它们填充了父视图的宽度。

一个重要的警告:如果你想让你的左视图回到图片中,你不仅必须将它添加回视图层次结构,而且你还必须同时重新建立所有的约束条件。这意味着当视图再次显示时,您需要一种方法将视图与其标签之间的10pt间距限制放回。

当set hidden = YES时,将uiview和标签之间的约束连接为IBOutlet,并将优先级成员设置为较小的值

在运行时期间添加或删除约束是会影响性能的重量级操作。然而,有一个更简单的选择。

对于希望隐藏的视图,设置宽度约束。用一个领先的水平间隙约束其他视图到该视图。

要隐藏,将width约束的.constant更新为0.f。其他视图将自动向左移动以占据位置。

更多细节请看我的另一个答案:

如何在运行时改变标签约束?< / >

对于谷歌人来说:建立在Max的答案上,为了解决许多人注意到的填充问题,我只是增加了标签的高度,并使用该高度作为分隔符而不是实际的填充。这个想法可以扩展到任何包含视图的场景。

这里有一个简单的例子:

IB截图

在本例中,我将作者标签的高度映射到适当的IBOutlet:

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

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

我最终创建了2个xib。一个有左视图,一个没有。我在控制器中注册了这两个,然后决定在cellForRowAtIndexPath期间使用哪个。

它们使用相同的UITableViewCell类。缺点是在xib之间有一些重复的内容,但是这些单元格非常基本。好处是我不需要一堆代码来手动管理删除视图、更新约束等。

一般来说,这可能是一个更好的解决方案,因为它们在技术上是不同的布局,因此应该有不同的xib。

[self.table registerNib:[UINib nibWithNibName:@"TrackCell" bundle:nil] forCellReuseIdentifier:@"TrackCell"];
[self.table registerNib:[UINib nibWithNibName:@"TrackCellNoImage" bundle:nil] forCellReuseIdentifier:@"TrackCellNoImage"];


TrackCell *cell = [tableView dequeueReusableCellWithIdentifier:(appDelegate.showImages ? @"TrackCell" : @"TrackCellNoImage") forIndexPath:indexPath];

正如no_scene建议的那样,您可以通过在运行时改变约束的优先级来实现这一点。这对我来说容易得多,因为我有不止一个需要移除的阻塞视图。

下面是使用ReactiveCocoa的代码片段:

RACSignal* isViewOneHiddenSignal = RACObserve(self.viewModel, isViewOneHidden);
RACSignal* isViewTwoHiddenSignal = RACObserve(self.viewModel, isViewTwoHidden);
RACSignal* isViewThreeHiddenSignal = RACObserve(self.viewModel, isViewThreeHidden);
RAC(self.viewOne, hidden) = isViewOneHiddenSignal;
RAC(self.viewTwo, hidden) = isViewTwoHiddenSignal;
RAC(self.viewThree, hidden) = isViewThreeHiddenSignal;


RAC(self.viewFourBottomConstraint, priority) = [[[[RACSignal
combineLatest:@[isViewOneHiddenSignal,
isViewTwoHiddenSignal,
isViewThreeHiddenSignal]]
and]
distinctUntilChanged]
map:^id(NSNumber* allAreHidden) {
return [allAreHidden boolValue] ? @(780) : @(UILayoutPriorityDefaultHigh);
}];


RACSignal* updateFramesSignal = [RACObserve(self.viewFourBottomConstraint, priority) distinctUntilChanged];
[updateFramesSignal
subscribeNext:^(id x) {
@strongify(self);
[self.view setNeedsUpdateConstraints];
[UIView animateWithDuration:0.3 animations:^{
[self.view layoutIfNeeded];
}];
}];

我的项目使用了UILabel的自定义@IBDesignable子类(以确保颜色、字体、插图等的一致性),我已经实现了如下内容:

override func intrinsicContentSize() -> CGSize {
if hidden {
return CGSizeZero
} else {
return super.intrinsicContentSize()
}
}

这允许label子类参与自动布局,但隐藏时不占用空间。

为了帮助别人,我构建了一个使用视觉格式约束的helper类。我在我当前的应用程序中使用它。

AutolayoutHelper

它可能有点适合我的需要,但您可能会发现它很有用,或者您可能想修改它并创建自己的帮助器。

我必须感谢蒂姆的以上回答,这个关于UIScrollView的答案和这个教程

对于那些只支持iOS 8 +的人,有一个新的布尔属性活跃的。这将有助于仅动态启用所需的约束

< p >注:约束出口必须是<强大的>强大的,而不是weak < br > < br > 例子:< / >强

@IBOutlet weak var optionalView: UIView!
@IBOutlet var viewIsVisibleConstraint: NSLayoutConstraint!
@IBOutlet var viewIsHiddenConstraint: NSLayoutConstraint!


func showView() {
optionalView.isHidden = false
viewIsVisibleConstraint.isActive = true
viewIsHiddenConstraint.isActive = false
}


func hideView() {
optionalView.isHidden = true
viewIsVisibleConstraint.isActive = false
viewIsHiddenConstraint.isActive = true
}
同样,为了修复故事板中的错误,你需要取消这些约束之一的Installed复选框。 < br > < br > UIStackView (iOS 9 +)
另一个选项是用UIStackView包装视图。一旦视图被隐藏UIStackView将自动更新布局

在我的例子中,我将高度限制的常量设置为0.0f,并将hidden属性设置为YES

为了再次显示视图(带子视图),我做了相反的操作:我将高度常数设置为非零值,并将hidden属性设置为NO

在本例中,我将Author标签的高度映射到适当的IBOutlet:

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

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

cell.authorLabelHeight.constant = 0;

enter image description here enter image description here < / p >

UIStackView的任何子视图上的hidden属性被改变时,UIStackView会自动重新定位它的视图(iOS 9+)。

UIView.animateWithDuration(1.0) { () -> Void in
self.mySubview.hidden = !self.mySubview.hidden
}

在这个WWDC视频中跳转到11:48进行演示:

自动布局的奥秘,第一部分

使用两个UIStackView水平和垂直,当堆栈中的某些子视图视图被隐藏时,其他堆栈子视图将被移动,使用Distribution ->按比例填充垂直堆栈中有两个UILabels,并且需要为第一个UIViewenter image description here设置宽度和高度约束

以下是我如何重新对齐我的uiviews来得到你的解决方案:

  1. 拖拽一个UIImageView放到左边。
  2. 拖拽一个UIView并把它放到UIImageView的右边。
  3. 在UIView中拖放两个uilabel,它的前导约束和尾随约束为零。
  4. 将包含2个标签的UIView的前置约束设置为superview而不是UIImagView。
  5. 如果UIImageView被隐藏,设置前置约束常量为10像素到superview。ELSE,设置前置约束常量为10px + UIImageView。宽度+ 10像素。

我自己制定了一个拇指规则。当你必须隐藏/显示任何约束可能受到影响的uiview时,在uiview中添加所有受影响/依赖的子视图,并以编程方式更新其前/后/上/下约束常量。

这是一个老问题,但我仍然希望它能有所帮助。来自Android,在这个平台上,你有一个方便的方法isVisible隐藏它从视图,但也没有考虑帧时,自动布局绘制视图。

使用extension和“extend”uiview,你可以在ios中做类似的功能(不确定为什么它不在UIKit中)这里是swift 3中的实现:

    func isVisible(_ isVisible: Bool) {
self.isHidden = !isVisible
self.translatesAutoresizingMaskIntoConstraints = isVisible
if isVisible { //if visible we remove the hight constraint
if let constraint = (self.constraints.filter{$0.firstAttribute == .height}.first){
self.removeConstraint(constraint)
}
} else { //if not visible we add a constraint to force the view to have a hight set to 0
let height = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal , toItem: nil, attribute: .notAnAttribute, multiplier: 0, constant: 0)
self.addConstraint(height)
}
self.layoutIfNeeded()
}

正确的方法是禁用isActive = false的约束。但是请注意,取消激活约束会删除并释放它,因此必须为它们提供强出口。

试试这个,我已经实现了下面的代码,

我在ViewController上有一个视图,其中添加了其他三个视图,当任何视图被隐藏时,其他两个视图将移动,遵循以下步骤。 < / p >

1. viewcontroller.h文件

#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@property (strong, nonatomic) IBOutlet UIView *viewOne;
@property (strong, nonatomic) IBOutlet UIView *viewTwo;
@property (strong, nonatomic) IBOutlet UIView *viewThree;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewOneWidth;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewTwoWidth;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewThreeWidth;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewBottomWidth;
@end

2. viewcontroller.m

 #import "ViewController.h"
@interface ViewController ()
{
CGFloat viewOneWidthConstant;
CGFloat viewTwoWidthConstant;
CGFloat viewThreeWidthConstant;
CGFloat viewBottomWidthConstant;
}
@end


@implementation ViewController
@synthesize viewOne, viewTwo, viewThree;


- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a
nib.


/*
0  0   0
0  0   1
0  1   0
0  1   1
1  0   0
1  0   1
1  1   0
1  1   1
*/


//    [viewOne setHidden:NO];
//    [viewTwo setHidden:NO];
//    [viewThree setHidden:NO];


//    [viewOne setHidden:NO];
//    [viewTwo setHidden:NO];
//    [viewThree setHidden:YES];


//    [viewOne setHidden:NO];
//    [viewTwo setHidden:YES];
//    [viewThree setHidden:NO];


//    [viewOne setHidden:NO];
//    [viewTwo setHidden:YES];
//    [viewThree setHidden:YES];




//    [viewOne setHidden:YES];
//    [viewTwo setHidden:NO];
//    [viewThree setHidden:NO];


//    [viewOne setHidden:YES];
//    [viewTwo setHidden:NO];
//    [viewThree setHidden:YES];


//    [viewOne setHidden:YES];
//    [viewTwo setHidden:YES];
//    [viewThree setHidden:NO];


//    [viewOne setHidden:YES];
//    [viewTwo setHidden:YES];
//    [viewThree setHidden:YES];


[self hideShowBottomBar];
}


- (void)hideShowBottomBar
{
BOOL isOne = !viewOne.isHidden;
BOOL isTwo = !viewTwo.isHidden;
BOOL isThree = !viewThree.isHidden;


viewOneWidthConstant = _viewOneWidth.constant;
viewTwoWidthConstant = _viewTwoWidth.constant;
viewThreeWidthConstant = _viewThreeWidth.constant;
viewBottomWidthConstant = _viewBottomWidth.constant;


if (isOne && isTwo && isThree) {
// 0    0   0
_viewOneWidth.constant = viewBottomWidthConstant / 3;
_viewTwoWidth.constant = viewBottomWidthConstant / 3;
_viewThreeWidth.constant = viewBottomWidthConstant / 3;
}
else if (isOne && isTwo && !isThree) {
// 0    0   1
_viewOneWidth.constant = viewBottomWidthConstant / 2;
_viewTwoWidth.constant = viewBottomWidthConstant / 2;
_viewThreeWidth.constant = 0;
}
else if (isOne && !isTwo && isThree) {
// 0    1   0
_viewOneWidth.constant = viewBottomWidthConstant / 2;
_viewTwoWidth.constant = 0;
_viewThreeWidth.constant = viewBottomWidthConstant / 2;
}
else if (isOne && !isTwo && !isThree) {
// 0    1   1
_viewOneWidth.constant = viewBottomWidthConstant;
_viewTwoWidth.constant = 0;
_viewThreeWidth.constant = 0;
}
else if (!isOne && isTwo && isThree) {
// 1    0   0
_viewOneWidth.constant = 0;
_viewTwoWidth.constant = viewBottomWidthConstant / 2;
_viewThreeWidth.constant = viewBottomWidthConstant / 2;
}
else if (!isOne && isTwo && !isThree) {
// 1    0   1
_viewOneWidth.constant = 0;
_viewTwoWidth.constant = viewBottomWidthConstant;
_viewThreeWidth.constant = 0;
}
else if (!isOne && !isTwo && isThree) {
// 1    1   0
_viewOneWidth.constant = 0;
_viewTwoWidth.constant = 0;
_viewThreeWidth.constant = viewBottomWidthConstant;
}
else if (isOne && isTwo && isThree) {
// 1    1   1
_viewOneWidth.constant = 0;
_viewTwoWidth.constant = 0;
_viewThreeWidth.constant = 0;
}
}


- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end

enter image description here enter image description here enter image description here < / p >

希望这个逻辑会对一些人有所帮助。

我将使用水平堆栈视图。当子视图被隐藏时,它可以移除框架。

在下面的图像中,红色视图是您的内容的实际容器,并有10pt的尾部空间到橙色的父视图(ShowHideView),然后只需将ShowHideView连接到IBOutlet并以编程方式显示/隐藏/删除它。

  1. 这是视图可见/安装的时候。

view is visible

  1. 这是当视图被隐藏/未安装时。

view is hidden/removed .

这是另一个使用优先级约束的解决方案。其思想是将宽度设置为0。

  1. 创建容器视图(橙色)并设置宽度。 李enter image description here < / p > < / >

  2. 创建内容视图(红色)并设置尾部空格10pt为superview(橙色)。注意尾随空间约束,有两个优先级不同的尾随约束。低(=10)和高(<=10)。这对于避免歧义很重要。 李enter image description here < / p > < / >

  3. 将橙色视图的宽度设置为0以隐藏视图。 李enter image description here < / p > < / >

我认为这是最简单的答案。请验证它是否有效:

        StackFullView.layer.isHidden = true
Task_TopSpaceSections.constant = 0.   //your constraint of top view

检查这里https://www.youtube.com/watch?v=EBulMWMoFuw

最简单的解决方案是使用UIStackView(水平)。添加到堆栈视图:带标签的第一个视图和第二个视图。 然后将first view的isHidden属性设置为false。 所有约束都将自动计算和更新。< / p >

只需使用UIStackView和一切将工作正常。 不用担心其他约束,UIStackView会自动处理空格

而不是隐藏视图,创建宽度约束,并在代码中更改为0当你想隐藏UIView。

这可能是最简单的方法。此外,它将保存视图,如果您想再次显示它,则不需要重新创建它(在表单元格中使用非常理想)。要更改常量值,您需要创建一个常量引用出口(与为视图创建出口的方式相同)。

对于这个特定的布局,要处理的约束是被隐藏的视图上的'leading'约束。下面的理论在各个方面都适用。

1:设置所有的约束,当所有的视图都是可见的时候,你想让它看起来怎么样。

2:给你想要隐藏的视图添加第二个'leading'约束。这将暂时打破限制。

3:将原来的前导约束的优先级更改为'999' -这将优先级给你的新约束,它将在1000,没有约束将被打破。

4:将新的约束从'leading=leading'更改为' trails =leading'。这将移动您想要隐藏的视图,使其远离父视图的前缘。

5:切换新约束的isActive值现在会切换,如果它在视图中或在视图外。在设置可见性为true/false的同时,将其设置为true/false。例如:

@IBOutlet var avatar:UIImage!
@IBOutlet var avatarLeadHid:NSLayoutConstraint!


func hideAvatar() {
self.avatar.isHidden = true
self.avatarLeadHid.isActive = true
}


func showAvatar() {
self.avatar.isHidden = false
self.avatarLeadHid.isActive = false
}

额外的好处:你可以调整新的hide -constraint的“常量”值,以改变视图隐藏时使用的填充/边距。这个值可以是负数。

额外的好处:只需切换hide -constraint上的“Installed”复选框,就可以在不运行任何代码的情况下,在Interface Builder中看到你的布局。

进一步帮助:我做了一个视频,展示了我做得更好的点列表:https://youtu.be/3tGEwqtQ-iU