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

366

我已经在IB中设计了自定义单元格,并将其子类化,并将我的输出连接到我的自定义类。我在单元格内容中有三个子视图,它们是:UIView(cdView)和两个标签(titleLabel和emailLabel)。根据每行可用的数据,有时我希望在我的单元格中显示UIView和两个标签,有时只显示两个标签。我尝试设置约束,以便如果我将UIView属性设置为隐藏或从superview中删除它,那么这两个标签将向左移动。我尝试将UIView的前导约束设置为Superview(Cell content)10px,UILabels的前导约束设置为下一个视图(UIView)10px。稍后在我的代码中。

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

    Record *record = [self.records objectAtIndex:indexPath.row];
    
    if ([record.imageURL is equalToString:@""]) {
         cell.cdView.hidden = YES;
    }
}

我正在隐藏我的cell.cdView,希望标签移到左边,但它们仍然停留在Cell中相同的位置。我尝试将cell.cdView从superview中删除,但也没有起作用。我附加了一张图片以澄清我的意思。

cell

我知道如何通过编程实现这一点,但我不想要那个解决方案。我想要的是在IB中设置约束,并期望我的子视图可以根据其他视图的删除或隐藏而动态移动。是否可能使用自动布局在IB中实现这一点?

.....

更改约束值运行时 - 检查此答案 - Kampai
2
对于这种特定情况,您也可以使用UIStackView。当您隐藏cd时,标签将占据它们的空间。 - Marco Pappalardo
@MarcoPappalardo 这似乎是唯一正确的解决方案。 - Koen
24个回答

7
针对这个特定的布局,需要处理的约束是被隐藏视图上的 "leading "约束。以下理论将适用于所有方向。
1:设置所有约束以使其在所有视图可见时显示所需外观。
2:向要隐藏的视图添加第二个 'leading' 约束。这将暂时中断约束。
3:将原始 leading 约束的优先级更改为“999”,这将为新约束赋予1000的优先级,不会再产生任何约束破坏。
4:将新约束从 'leading=leading' 更改为 'trailing=leading'。这将使您要隐藏的视图移出其父视图的前导边缘,从而将其移到一边。
5:现在可以切换新约束的isActive值来切换它是否在视图中或在视图之外。同时将其可见性设置为 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
}

奖励:您可以调整新隐藏约束的“常数”值,以便在视图隐藏时更改要使用的填充/边距。该值可以是负数。

额外奖励:通过切换hider-constraint上的“安装”复选框,可以在没有运行任何代码的情况下从接口构建器中查看布局的外观。

进一步帮助:我制作了一个视频,展示了我所做的事情比列表更好:https://youtu.be/3tGEwqtQ-iU


很多时候,动画约束看起来更好,因此不要突然更改约束的值或将其停用,而是为layoutConstraint设置偏移值,然后使用UIView.animate(widthDuration: n) { view.layoutIfNeeded() }从约束的先前“constant”值到新值过渡动画。有很多例子:https://stackoverflow.com/a/52465169/2079103 - clearlight

6

使用两个UIStackView,一个水平的,一个垂直的。当一些子视图在堆栈中被隐藏时,其他堆栈子视图将会移动。对于垂直堆栈中的两个UILabel,使用Distribution -> Fill Proportionally,并需要为第一个UIView设置宽度和高度约束。enter image description here


2
尝试这个,我已经实现了下面的代码。
在`ViewController`上有一个视图,在其中添加了另外三个视图。当任何一个视图被隐藏时,其他两个视图将移动。请按照以下步骤操作。
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

希望这个逻辑对某个人有所帮助。


2
在我的情况下,我将 高度约束 的常量设置为 0.0f,并将 hidden 属性设置为 YES
要再次显示带有子视图的视图,我执行了相反的操作:将高度常量设置为非零值,并将 hidden 属性设置为 NO

2
我将使用水平堆栈视图。当子视图隐藏时,它可以移除框架。
在下面的图片中,红色视图是您内容的实际容器,并且有10个点的尾随空间到橙色的父视图(ShowHideView),然后只需将ShowHideView连接到IBOutlet并在程序中以编程方式显示/隐藏/移除它。
1. 这是视图可见/已安装时的情况。

view is visible

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

view is hidden/removed


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

1

不要隐藏视图,创建宽度约束并在需要隐藏UIView时将其更改为0的代码。

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


先生,这是最简单的解决方案。只要您有高度或宽度约束,这个方法就可以很好地工作。 - Droid Chris

1
这是使用优先约束的另一种解决方案。思路是将宽度设置为0。
1.创建容器视图(橙色),并设置宽度。 enter image description here 2.创建内容视图(红色),并将其尾部间距设置为10pt以相对于父视图(橙色)定位。注意尾部间距约束,有两个不同优先级的尾部约束。低(=10)和高(<=10)。这很重要,以避免歧义。 enter image description here 3.将橙色视图的宽度设置为0以隐藏该视图。 enter image description here

0

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

0

这是一个老问题,但我希望它仍然有所帮助。如果您来自Android平台,那么您可以使用一个方便的方法isVisible来隐藏视图,但在自动布局绘制视图时不会考虑其框架。

使用扩展和“扩展”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()
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接