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

时间:2013-08-05 19:16:01

标签: ios iphone cocoa-touch interface-builder autolayout

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

-(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中保持相同的位置。我试图从superview中删除cell.cdView,但它也没有用。我附上图片以澄清我的意思。

cell

我知道如何以编程方式执行此操作,而我并不是在寻找解决方案。我想要的是在IB中设置约束,并且如果删除或隐藏其他视图,我希望我的子视图将动态移动。是否可以在具有自动布局的IB中执行此操作?

.....

24 个答案:

答案 0 :(得分:349)

这是可能的,但你必须做一些额外的工作。有一些概念性的东西要先摆脱困境:

  • 隐藏视图,即使他们没有绘制,仍然参与在自动布局中,通常保留他们的框架,将其他相关视图留在他们的位置。
  • 从超级视图中删除视图时,所有相关约束也会从该视图层次结构中删除。

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

  • 如果您将左侧视图设置为隐藏,则标签会保留在原位,因为左侧视图仍占用空间(即使它不可见)。
  • 如果删除左侧视图,您的标签可能会受到歧义限制,因为您的标签左边缘不再受限制。

您需要做的是明智地过度约束您的标签。单独保留现有约束(10pts空间到另一个视图),但添加另一个约束:使标签的左边缘与超级视图的左边缘相差10pts,具有非必需的优先级(默认的高优先级可能效果很好)。 / p>

然后,当您希望它们向左移动时,完全删除左视图。左视图的强制10pt约束将与其相关的视图一起消失,并且您将只留下一个高优先级约束,标签距其超视图10pts。在下一个布局过程中,这应该使它们向左扩展,直到它们填充超视图的宽度,但是围绕边缘的间距。

一个重要的警告:如果您希望将左侧视图放回到图片中,则不仅需要将其添加回视图层次结构中,而且还必须重新建立其所有约束同一时间。这意味着只要再次显示该视图,您就需要一种方法在视图及其标签之间放置10pt间距约束。

答案 1 :(得分:199)

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

对于要隐藏的视图,请设置宽度约束。使用与该视图的前导水平间隙约束其他视图。

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

有关详细信息,请参阅我的其他答案:

How to change label constraints during runtime?

答案 2 :(得分:77)

对于仅支持 iOS 8 + 的用户,有一个新的布尔属性active。它将有助于动态启用所需的约束

P.S。约束出口必须,而不是弱

示例:

@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复选框。

UIStackView (iOS 9 +)
还有一个选项是将您的观点包装在UIStackView中。隐藏视图后UIStackView将自动更新布局

答案 3 :(得分:56)

UIStackView属性在其任何子视图(iOS 9+)上更改时,

hidden会自动重新定位其视图。

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

跳转到此WWDC视频中的11:48进行演示:

Mysteries of Auto Layout, Part 1

答案 4 :(得分:16)

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

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

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

答案 5 :(得分:13)

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

这是一个简单的例子:

IB Screenshot

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

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

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

答案 6 :(得分:8)

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

答案 7 :(得分:8)

我最终做的是创建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];

答案 8 :(得分:6)

使用两个UIStackView水平和垂直,当隐藏堆栈中的某个子视图时,将移动其他堆栈子视图,使用Distribution - >使用两个UILabel填充垂直堆栈,并为第一个UIView设置宽度和高度设置enter image description here

答案 9 :(得分:6)

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

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

当我将约束的高度设置为0.0f时,我们保留" padding",因为Play按钮的高度允许它。

cell.authorLabelHeight.constant = 0;

enter image description here enter image description here

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

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

答案 11 :(得分:1)

在我的情况下,我将身高约束的常量设置为0.0f,并将hidden属性设置为YES

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

答案 12 :(得分:1)

我将使用水平stackview。隐藏子视图时,它可以删除框架。

在下图中,红色视图是内容的实际容器,并且具有10pt尾随空间到橙色超级视图(ShowHideView),然后将ShowHideView连接到IBOutlet并以编程方式显示/隐藏/删除它。

  1. 这是视图可见/已安装的时间。
  2. view is visible

    1. 这是隐藏/未安装视图的时间。
    2. view is hidden/removed

答案 13 :(得分:1)

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

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

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

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

答案 14 :(得分:1)

只需使用UIStackView,一切都会正常。 无需担心其他约束,UIStackView将自动处理空间。

答案 15 :(得分:1)

对于这个特定的布局,要使用的约束是被隐藏的视图上的“前导”约束。不过,以下理论将适用于所有方向。

1:设置所有约束条件,当所有视图都可见时,它的外观如何。

2:向要隐藏的视图添加第二个“引导”约束。这将暂时打破限制。

3:将原始前导约束的优先级更改为“999” - 这将优先考虑您的新约束,该约束将为 1000,并且不会再破坏任何约束。

4: 将新约束从 'leading=leading' 更改为 'trailing=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
}

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

额外奖励:只需切换隐藏约束上的“已安装”复选框,即可在界面生成器中查看布局的外观,而无需运行任何代码。

进一步帮助:我制作了一个视频,展示了我比一系列要点做得更好的地方:https://youtu.be/3tGEwqtQ-iU

答案 16 :(得分:0)

如果这对某人有帮助,我构建了一个使用visual format约束的辅助类。我在我当前的应用程序中使用它。

AutolayoutHelper

它可能会根据我的需求量身定制,但您可能会发现它很有用,或者您可能想要修改它并创建自己的帮助程序。

我要感谢蒂姆的answer above,此answer about UIScrollView以及此tutorial

答案 17 :(得分: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];
        }];
    }];

答案 18 :(得分:0)

以下是我如何重新调整我的uiviews以获得解决方案:

  1. 拖放一个UIImageView并将其放在左侧。
  2. 拖放一个UIView并将其放在UIImageView的右侧。
  3. 在UIView中拖放两个UILabel,其前导和尾随约束为零。
  4. 将包含2个标签的UIView的前导约束设置为superview而不是UIImagView。
  5. 如果隐藏UIImageView,则将超前约束常量设置为10 px到superview。 ELSE,将前导约束常量设置为10 px + UIImageView.width + 10 px。
  6. 我创建了自己的拇指规则。每当您必须隐藏/显示其约束可能受影响的任何uiview时,在uiview中添加所有受影响/相关子视图,并以编程方式更新其前导/尾随/顶部/底部约束常量。

答案 19 :(得分:0)

这是一个老问题,但我希望它会有所帮助。来自Android,在这个平台上你有一个方便的方法isVisible来隐藏它从视图中,但是当autolayout绘制视图时也没有考虑框架。

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

答案 20 :(得分:0)

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

答案 21 :(得分:0)

我认为这是最简单的答案,请检查它是否运行正常

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

在此处https://www.youtube.com/watch?v=EBulMWMoFuw

答案 22 :(得分:0)

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

答案 23 :(得分:0)

创建宽度限制,而不是隐藏视图,并在要隐藏UIView的代码中将其更改为0。

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